github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 // Testing utilities 5 import { Test } from "forge-std/Test.sol"; 6 import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; 7 import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; 8 import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; 9 import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; 10 import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; 11 12 contract OptimistAllowlist_Initializer is Test { 13 event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); 14 15 address internal alice_allowlistAttestor; 16 address internal sally_coinbaseQuestAttestor; 17 address internal ted; 18 19 uint256 internal bobPrivateKey; 20 address internal bob; 21 22 AttestationStation attestationStation; 23 OptimistAllowlist optimistAllowlist; 24 OptimistInviter optimistInviter; 25 26 // Helps with EIP-712 signature generation 27 OptimistInviterHelper optimistInviterHelper; 28 29 function setUp() public { 30 alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); 31 sally_coinbaseQuestAttestor = makeAddr("sally_coinbaseQuestAttestor"); 32 ted = makeAddr("ted"); 33 34 bobPrivateKey = 0xB0B0B0B0; 35 bob = vm.addr(bobPrivateKey); 36 vm.label(bob, "bob"); 37 38 // Give alice and bob and sally some ETH 39 vm.deal(alice_allowlistAttestor, 1 ether); 40 vm.deal(sally_coinbaseQuestAttestor, 1 ether); 41 vm.deal(bob, 1 ether); 42 vm.deal(ted, 1 ether); 43 44 _initializeContracts(); 45 } 46 47 function attestAllowlist(address _about) internal { 48 AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); 49 // we are using true but it can be any non empty value 50 attestationData[0] = AttestationStation.AttestationData({ 51 about: _about, 52 key: optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), 53 val: bytes("true") 54 }); 55 vm.prank(alice_allowlistAttestor); 56 attestationStation.attest(attestationData); 57 } 58 59 function attestCoinbaseQuest(address _about) internal { 60 AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); 61 // we are using true but it can be any non empty value 62 attestationData[0] = AttestationStation.AttestationData({ 63 about: _about, 64 key: optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), 65 val: bytes("true") 66 }); 67 vm.prank(sally_coinbaseQuestAttestor); 68 attestationStation.attest(attestationData); 69 } 70 71 function inviteAndClaim(address claimer) internal { 72 address[] memory addresses = new address[](1); 73 addresses[0] = bob; 74 75 vm.prank(alice_allowlistAttestor); 76 77 // grant invites to Bob; 78 optimistInviter.setInviteCounts(addresses, 3); 79 80 // issue a new invite 81 OptimistInviter.ClaimableInvite memory claimableInvite = 82 optimistInviterHelper.getClaimableInviteWithNewNonce(bob); 83 84 // EIP-712 sign with Bob's private key 85 bytes memory signature = _getSignature(bobPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); 86 87 bytes32 hashedCommit = keccak256(abi.encode(claimer, signature)); 88 89 // commit the invite 90 vm.prank(claimer); 91 optimistInviter.commitInvite(hashedCommit); 92 93 // wait minimum commitment period 94 vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); 95 96 // reveal and claim the invite 97 optimistInviter.claimInvite(claimer, claimableInvite, signature); 98 } 99 100 /// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs. 101 function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) { 102 (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest); 103 104 bytes memory signature = abi.encodePacked(r, s, v); 105 return signature; 106 } 107 108 function _initializeContracts() internal { 109 attestationStation = new AttestationStation(); 110 111 optimistInviter = new OptimistInviter(alice_allowlistAttestor, attestationStation); 112 optimistInviter.initialize("OptimistInviter"); 113 114 optimistAllowlist = new OptimistAllowlist( 115 attestationStation, alice_allowlistAttestor, sally_coinbaseQuestAttestor, address(optimistInviter) 116 ); 117 118 optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); 119 } 120 } 121 122 contract OptimistAllowlistTest is OptimistAllowlist_Initializer { 123 function test_constructor_succeeds() external { 124 // expect attestationStation to be set 125 assertEq(address(optimistAllowlist.ATTESTATION_STATION()), address(attestationStation)); 126 assertEq(optimistAllowlist.ALLOWLIST_ATTESTOR(), alice_allowlistAttestor); 127 assertEq(optimistAllowlist.COINBASE_QUEST_ATTESTOR(), sally_coinbaseQuestAttestor); 128 assertEq(address(optimistAllowlist.OPTIMIST_INVITER()), address(optimistInviter)); 129 } 130 131 /// @notice Base case, a account without any relevant attestations should not be able to mint. 132 function test_isAllowedToMint_withoutAnyAttestations_fails() external { 133 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 134 } 135 136 /// @notice After receiving a valid allowlist attestation, the account should be able to mint. 137 function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external { 138 attestAllowlist(bob); 139 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 140 } 141 142 /// @notice After receiving a valid attestation from the Coinbase Quest attestor, 143 /// the account should be able to mint. 144 function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external { 145 attestCoinbaseQuest(bob); 146 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 147 } 148 149 /// @notice Account that received an attestation from the OptimistInviter contract by going 150 /// through the claim invite flow should be able to mint. 151 function test_isAllowedToMint_fromInvite_succeeds() external { 152 inviteAndClaim(bob); 153 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 154 } 155 156 /// @notice Attestation from the wrong allowlist attestor should not allow minting. 157 function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external { 158 // Ted is not the allowlist attestor 159 vm.prank(ted); 160 attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); 161 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 162 } 163 164 /// @notice Coinbase quest attestation from wrong attestor should not allow minting. 165 function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external { 166 // Ted is not the coinbase quest attestor 167 vm.prank(ted); 168 attestationStation.attest(bob, optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), bytes("true")); 169 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 170 } 171 172 /// @notice Claiming an invite on the non-official OptimistInviter contract should not allow 173 /// minting. 174 function test_isAllowedToMint_fromWrongOptimistInviter_fails() external { 175 vm.prank(ted); 176 attestationStation.attest(bob, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, bytes("true")); 177 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 178 } 179 180 /// @notice Having multiple signals, even if one is invalid, should still allow minting. 181 function test_isAllowedToMint_withMultipleAttestations_succeeds() external { 182 attestAllowlist(bob); 183 attestCoinbaseQuest(bob); 184 inviteAndClaim(bob); 185 186 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 187 188 // A invalid attestation, as Ted is not allowlist attestor 189 vm.prank(ted); 190 attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); 191 192 // Since Bob has at least one valid attestation, he should be allowed to mint 193 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 194 } 195 196 /// @notice Having falsy attestation value should not allow minting. 197 function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external { 198 // First sends correct attestation 199 attestAllowlist(bob); 200 201 bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); 202 vm.expectEmit(true, true, true, false); 203 emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("dsafsds")); 204 205 // Invalidates existing attestation 206 vm.prank(alice_allowlistAttestor); 207 attestationStation.attest(bob, key, bytes("")); 208 209 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 210 } 211 212 /// @notice Having falsy attestation value from Coinbase attestor should not allow minting. 213 function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external { 214 // First sends correct attestation 215 attestAllowlist(bob); 216 217 bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); 218 vm.expectEmit(true, true, true, true); 219 emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("")); 220 221 // Invalidates existing attestation 222 vm.prank(alice_allowlistAttestor); 223 attestationStation.attest(bob, key, bytes("")); 224 225 assertFalse(optimistAllowlist.isAllowedToMint(bob)); 226 } 227 }