github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity >=0.6.2 <0.9.0; 3 4 // Testing utilities 5 import { Test } from "forge-std/Test.sol"; 6 import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; 7 import { Optimist } from "src/periphery/op-nft/Optimist.sol"; 8 import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; 9 import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; 10 import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; 11 import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; 12 import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 13 14 interface IMulticall3 { 15 struct Call3 { 16 address target; 17 bool allowFailure; 18 bytes callData; 19 } 20 21 struct Result { 22 bool success; 23 bytes returnData; 24 } 25 26 function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData); 27 } 28 29 library Multicall { 30 bytes internal constant code = 31 hex"6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033"; 32 address internal constant addr = 0xcA11bde05977b3631167028862bE2a173976CA11; 33 } 34 35 contract Optimist_Initializer is Test { 36 event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); 37 event Initialized(uint8); 38 event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); 39 40 string constant name = "Optimist name"; 41 string constant symbol = "OPTIMISTSYMBOL"; 42 string constant base_uri = "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes"; 43 AttestationStation attestationStation; 44 Optimist optimist; 45 OptimistAllowlist optimistAllowlist; 46 OptimistInviter optimistInviter; 47 48 // Helps with EIP-712 signature generation 49 OptimistInviterHelper optimistInviterHelper; 50 51 // To test multicall for claiming and minting in one call 52 IMulticall3 multicall3; 53 54 address internal carol_baseURIAttestor; 55 address internal alice_allowlistAttestor; 56 address internal eve_inviteGranter; 57 address internal ted_coinbaseAttestor; 58 address internal bob; 59 address internal sally; 60 61 /// @notice BaseURI attestor sets the baseURI of the Optimist NFT. 62 function _attestBaseURI(string memory _baseUri) internal { 63 bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY(); 64 AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); 65 attestationData[0] = 66 AttestationStation.AttestationData(address(optimist), baseURIAttestationKey, bytes(_baseUri)); 67 68 vm.expectEmit(true, true, true, true, address(attestationStation)); 69 emit AttestationCreated(carol_baseURIAttestor, address(optimist), baseURIAttestationKey, bytes(_baseUri)); 70 vm.prank(carol_baseURIAttestor); 71 attestationStation.attest(attestationData); 72 } 73 74 /// @notice Allowlist attestor creates an attestation for an address. 75 function _attestAllowlist(address _about) internal { 76 bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); 77 AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); 78 // we are using true but it can be any non empty value 79 attestationData[0] = 80 AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); 81 82 vm.expectEmit(true, true, true, true, address(attestationStation)); 83 emit AttestationCreated(alice_allowlistAttestor, _about, attestationKey, bytes("true")); 84 85 vm.prank(alice_allowlistAttestor); 86 attestationStation.attest(attestationData); 87 88 assertTrue(optimist.isOnAllowList(_about)); 89 } 90 91 /// @notice Coinbase Quest attestor creates an attestation for an address. 92 function _attestCoinbaseQuest(address _about) internal { 93 bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(); 94 AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); 95 // we are using true but it can be any non empty value 96 attestationData[0] = 97 AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); 98 99 vm.expectEmit(true, true, true, true, address(attestationStation)); 100 emit AttestationCreated(ted_coinbaseAttestor, _about, attestationKey, bytes("true")); 101 102 vm.prank(ted_coinbaseAttestor); 103 attestationStation.attest(attestationData); 104 105 assertTrue(optimist.isOnAllowList(_about)); 106 } 107 108 /// @notice Issues invite, then claims it using the claimer's address. 109 function _inviteAndClaim(address _about) internal { 110 uint256 inviterPrivateKey = 0xbeefbeef; 111 address inviter = vm.addr(inviterPrivateKey); 112 113 address[] memory addresses = new address[](1); 114 addresses[0] = inviter; 115 116 vm.prank(eve_inviteGranter); 117 118 // grant invites to Inviter; 119 optimistInviter.setInviteCounts(addresses, 3); 120 121 // issue a new invite 122 OptimistInviter.ClaimableInvite memory claimableInvite = 123 optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); 124 125 // EIP-712 sign with Inviter's private key 126 127 (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); 128 bytes memory signature = abi.encodePacked(r, s, v); 129 130 bytes32 hashedCommit = keccak256(abi.encode(_about, signature)); 131 132 // commit the invite 133 vm.prank(_about); 134 optimistInviter.commitInvite(hashedCommit); 135 136 // wait minimum commitment period 137 vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); 138 139 // reveal and claim the invite 140 optimistInviter.claimInvite(_about, claimableInvite, signature); 141 142 assertTrue(optimist.isOnAllowList(_about)); 143 } 144 145 /// @notice Mocks the allowlistAttestor to always return true for a given address. 146 function _mockAllowlistTrueFor(address _claimer) internal { 147 vm.mockCall( 148 address(optimistAllowlist), 149 abi.encodeWithSelector(OptimistAllowlist.isAllowedToMint.selector, _claimer), 150 abi.encode(true) 151 ); 152 153 assertTrue(optimist.isOnAllowList(_claimer)); 154 } 155 156 /// @notice Returns address as uint256. 157 function _getTokenId(address _owner) internal pure returns (uint256) { 158 return uint256(uint160(address(_owner))); 159 } 160 161 function setUp() public { 162 carol_baseURIAttestor = makeAddr("carol_baseURIAttestor"); 163 alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); 164 eve_inviteGranter = makeAddr("eve_inviteGranter"); 165 ted_coinbaseAttestor = makeAddr("ted_coinbaseAttestor"); 166 bob = makeAddr("bob"); 167 sally = makeAddr("sally"); 168 _initializeContracts(); 169 } 170 171 function _initializeContracts() internal { 172 attestationStation = new AttestationStation(); 173 vm.expectEmit(true, true, false, false); 174 emit Initialized(1); 175 176 optimistInviter = 177 new OptimistInviter({ _inviteGranter: eve_inviteGranter, _attestationStation: attestationStation }); 178 179 optimistInviter.initialize("OptimistInviter"); 180 181 // Initialize the helper which helps sign EIP-712 signatures 182 optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); 183 184 optimistAllowlist = new OptimistAllowlist({ 185 _attestationStation: attestationStation, 186 _allowlistAttestor: alice_allowlistAttestor, 187 _coinbaseQuestAttestor: ted_coinbaseAttestor, 188 _optimistInviter: address(optimistInviter) 189 }); 190 191 optimist = new Optimist({ 192 _name: name, 193 _symbol: symbol, 194 _baseURIAttestor: carol_baseURIAttestor, 195 _attestationStation: attestationStation, 196 _optimistAllowlist: optimistAllowlist 197 }); 198 199 multicall3 = IMulticall3(Multicall.addr); 200 vm.etch(Multicall.addr, Multicall.code); 201 } 202 } 203 204 contract OptimistTest is Optimist_Initializer { 205 /// @notice Check that constructor and initializer parameters are correctly set. 206 function test_initialize_succeeds() external { 207 // expect name to be set 208 assertEq(optimist.name(), name); 209 // expect symbol to be set 210 assertEq(optimist.symbol(), symbol); 211 // expect attestationStation to be set 212 assertEq(address(optimist.ATTESTATION_STATION()), address(attestationStation)); 213 assertEq(optimist.BASE_URI_ATTESTOR(), carol_baseURIAttestor); 214 } 215 216 /// @notice Bob should be able to mint an NFT if he is allowlisted 217 /// by the allowlistAttestor and has a balance of 0. 218 function test_mint_afterAllowlistAttestation_succeeds() external { 219 // bob should start with 0 balance 220 assertEq(optimist.balanceOf(bob), 0); 221 222 // allowlist bob 223 _attestAllowlist(bob); 224 225 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 226 227 // Check that the OptimistAllowlist is checked 228 bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); 229 vm.expectCall(address(optimistAllowlist), data); 230 231 // mint an NFT and expect mint transfer event to be emitted 232 vm.expectEmit(true, true, true, true); 233 emit Transfer(address(0), bob, _getTokenId(bob)); 234 vm.prank(bob); 235 optimist.mint(bob); 236 237 // expect the NFT to be owned by bob 238 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 239 assertEq(optimist.balanceOf(bob), 1); 240 } 241 242 /// @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter 243 /// and has a balance of 0. 244 function test_mint_afterInviteClaimed_succeeds() external { 245 // bob should start with 0 balance 246 assertEq(optimist.balanceOf(bob), 0); 247 248 // bob claims an invite 249 _inviteAndClaim(bob); 250 251 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 252 253 // Check that the OptimistAllowlist is checked 254 bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); 255 vm.expectCall(address(optimistAllowlist), data); 256 257 // mint an NFT and expect mint transfer event to be emitted 258 vm.expectEmit(true, true, true, true); 259 emit Transfer(address(0), bob, _getTokenId(bob)); 260 vm.prank(bob); 261 optimist.mint(bob); 262 263 // expect the NFT to be owned by bob 264 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 265 assertEq(optimist.balanceOf(bob), 1); 266 } 267 268 /// @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest 269 /// attestor and has a balance of 0. 270 function test_mint_afterCoinbaseQuestAttestation_succeeds() external { 271 // bob should start with 0 balance 272 assertEq(optimist.balanceOf(bob), 0); 273 274 // bob receives attestation from Coinbase Quest attestor 275 _attestCoinbaseQuest(bob); 276 277 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 278 279 // Check that the OptimistAllowlist is checked 280 bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); 281 vm.expectCall(address(optimistAllowlist), data); 282 283 // mint an NFT and expect mint transfer event to be emitted 284 vm.expectEmit(true, true, true, true); 285 emit Transfer(address(0), bob, _getTokenId(bob)); 286 vm.prank(bob); 287 optimist.mint(bob); 288 289 // expect the NFT to be owned by bob 290 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 291 assertEq(optimist.balanceOf(bob), 1); 292 } 293 294 /// @notice Multiple valid attestations should allow Bob to mint. 295 function test_mint_afterMultipleAttestations_succeeds() external { 296 // bob should start with 0 balance 297 assertEq(optimist.balanceOf(bob), 0); 298 299 // bob receives attestation from Coinbase Quest attestor 300 _attestCoinbaseQuest(bob); 301 302 // allowlist bob 303 _attestAllowlist(bob); 304 305 // bob claims an invite 306 _inviteAndClaim(bob); 307 308 assertTrue(optimistAllowlist.isAllowedToMint(bob)); 309 310 // Check that the OptimistAllowlist is checked 311 bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob); 312 vm.expectCall(address(optimistAllowlist), data); 313 314 // mint an NFT and expect mint transfer event to be emitted 315 vm.expectEmit(true, true, true, true); 316 emit Transfer(address(0), bob, _getTokenId(bob)); 317 vm.prank(bob); 318 optimist.mint(bob); 319 320 // expect the NFT to be owned by bob 321 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 322 assertEq(optimist.balanceOf(bob), 1); 323 } 324 325 /// @notice Sally should be able to mint a token on behalf of bob. 326 function test_mint_secondaryMinter_succeeds() external { 327 _mockAllowlistTrueFor(bob); 328 329 vm.expectEmit(true, true, true, true); 330 emit Transfer(address(0), bob, _getTokenId(bob)); 331 332 // mint as sally instead of bob 333 vm.prank(sally); 334 optimist.mint(bob); 335 336 // expect the NFT to be owned by bob 337 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 338 assertEq(optimist.balanceOf(bob), 1); 339 } 340 341 /// @notice Bob should not be able to mint an NFT if he is not allowlisted. 342 function test_mint_forNonAllowlistedClaimer_reverts() external { 343 vm.prank(bob); 344 vm.expectRevert("Optimist: address is not on allowList"); 345 optimist.mint(bob); 346 } 347 348 /// @notice Bob's tx should revert if he already minted. 349 function test_mint_forAlreadyMintedClaimer_reverts() external { 350 _attestAllowlist(bob); 351 352 // mint initial nft with bob 353 vm.prank(bob); 354 optimist.mint(bob); 355 // expect the NFT to be owned by bob 356 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 357 assertEq(optimist.balanceOf(bob), 1); 358 359 // attempt to mint again 360 vm.expectRevert("ERC721: token already minted"); 361 optimist.mint(bob); 362 } 363 364 /// @notice The baseURI should be set by attestation station by the baseURIAttestor. 365 function test_baseURI_returnsCorrectBaseURI_succeeds() external { 366 _attestBaseURI(base_uri); 367 368 bytes memory data = abi.encodeWithSelector( 369 attestationStation.attestations.selector, 370 carol_baseURIAttestor, 371 address(optimist), 372 optimist.BASE_URI_ATTESTATION_KEY() 373 ); 374 vm.expectCall(address(attestationStation), data); 375 vm.prank(carol_baseURIAttestor); 376 377 // assert baseURI is set 378 assertEq(optimist.baseURI(), base_uri); 379 } 380 381 /// @notice tokenURI should return the token uri for a minted token. 382 function test_tokenURI_returnsCorrectTokenURI_succeeds() external { 383 // we are using true but it can be any non empty value 384 _attestBaseURI(base_uri); 385 386 // mint an NFT 387 _mockAllowlistTrueFor(bob); 388 vm.prank(bob); 389 optimist.mint(bob); 390 391 // assert tokenURI is set 392 assertEq(optimist.baseURI(), base_uri); 393 assertEq( 394 optimist.tokenURI(_getTokenId(bob)), 395 "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e.json" 396 ); 397 } 398 399 /// @notice Should return the token id of the owner. 400 function test_tokenIdOfAddress_returnsOwnerID_succeeds() external { 401 uint256 willTokenId = 1024; 402 address will = address(1024); 403 404 _mockAllowlistTrueFor(will); 405 406 optimist.mint(will); 407 408 assertEq(optimist.tokenIdOfAddress(will), willTokenId); 409 } 410 411 /// @notice transferFrom should revert since Optimist is a SBT. 412 function test_transferFrom_soulbound_reverts() external { 413 _mockAllowlistTrueFor(bob); 414 415 // mint as bob 416 vm.prank(bob); 417 optimist.mint(bob); 418 419 // attempt to transfer to sally 420 vm.expectRevert(bytes("Optimist: soul bound token")); 421 vm.prank(bob); 422 optimist.transferFrom(bob, sally, _getTokenId(bob)); 423 424 // attempt to transfer to sally 425 vm.expectRevert(bytes("Optimist: soul bound token")); 426 vm.prank(bob); 427 optimist.safeTransferFrom(bob, sally, _getTokenId(bob)); 428 // attempt to transfer to sally 429 vm.expectRevert(bytes("Optimist: soul bound token")); 430 vm.prank(bob); 431 optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x")); 432 } 433 434 /// @notice approve should revert since Optimist is a SBT. 435 function test_approve_soulbound_reverts() external { 436 _mockAllowlistTrueFor(bob); 437 438 // mint as bob 439 vm.prank(bob); 440 optimist.mint(bob); 441 442 // attempt to approve sally 443 vm.prank(bob); 444 vm.expectRevert("Optimist: soul bound token"); 445 optimist.approve(address(attestationStation), _getTokenId(bob)); 446 447 assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); 448 } 449 450 /// @notice setApprovalForAll should revert since Optimist is a SBT. 451 function test_setApprovalForAll_soulbound_reverts() external { 452 _mockAllowlistTrueFor(bob); 453 454 // mint as bob 455 vm.prank(bob); 456 optimist.mint(bob); 457 vm.prank(alice_allowlistAttestor); 458 vm.expectRevert(bytes("Optimist: soul bound token")); 459 optimist.setApprovalForAll(alice_allowlistAttestor, true); 460 461 // expect approval amount to stil be 0 462 assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); 463 // isApprovedForAll should return false 464 assertEq(optimist.isApprovedForAll(alice_allowlistAttestor, alice_allowlistAttestor), false); 465 } 466 467 /// @notice Only owner should be able to burn token. 468 function test_burn_byOwner_succeeds() external { 469 _mockAllowlistTrueFor(bob); 470 471 // mint as bob 472 vm.prank(bob); 473 optimist.mint(bob); 474 475 // burn as bob 476 vm.prank(bob); 477 optimist.burn(_getTokenId(bob)); 478 479 // expect bob to have no balance now 480 assertEq(optimist.balanceOf(bob), 0); 481 } 482 483 /// @notice Non-owner attempting to burn token should revert. 484 function test_burn_byNonOwner_reverts() external { 485 _mockAllowlistTrueFor(bob); 486 487 // mint as bob 488 vm.prank(bob); 489 optimist.mint(bob); 490 491 vm.expectRevert("ERC721: caller is not token owner nor approved"); 492 // burn as Sally 493 vm.prank(sally); 494 optimist.burn(_getTokenId(bob)); 495 496 // expect bob to have still have the token 497 assertEq(optimist.balanceOf(bob), 1); 498 } 499 500 /// @notice Should support ERC-721 interface. 501 function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external { 502 bytes4 iface721 = type(IERC721).interfaceId; 503 // check that it supports ERC-721 interface 504 assertEq(optimist.supportsInterface(iface721), true); 505 } 506 507 /// @notice Checking that multi-call using the invite & claim flow works correctly, since the 508 /// frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite 509 /// and Optimist.mint will be batched 510 function test_multicall_batchingClaimAndMint_succeeds() external { 511 uint256 inviterPrivateKey = 0xbeefbeef; 512 address inviter = vm.addr(inviterPrivateKey); 513 514 address[] memory addresses = new address[](1); 515 addresses[0] = inviter; 516 517 vm.prank(eve_inviteGranter); 518 519 // grant invites to Inviter; 520 optimistInviter.setInviteCounts(addresses, 3); 521 522 // issue a new invite 523 OptimistInviter.ClaimableInvite memory claimableInvite = 524 optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); 525 526 // EIP-712 sign with Inviter's private key 527 528 (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); 529 bytes memory signature = abi.encodePacked(r, s, v); 530 531 bytes32 hashedCommit = keccak256(abi.encode(bob, signature)); 532 533 // commit the invite 534 vm.prank(bob); 535 optimistInviter.commitInvite(hashedCommit); 536 537 // wait minimum commitment period 538 vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); 539 540 IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2); 541 542 // First call is to claim the invite, receiving the attestation 543 calls[0] = IMulticall3.Call3({ 544 target: address(optimistInviter), 545 callData: abi.encodeWithSelector(optimistInviter.claimInvite.selector, bob, claimableInvite, signature), 546 allowFailure: false 547 }); 548 549 // Second call is to mint the Optimist NFT 550 calls[1] = IMulticall3.Call3({ 551 target: address(optimist), 552 callData: abi.encodeWithSelector(optimist.mint.selector, bob), 553 allowFailure: false 554 }); 555 556 multicall3.aggregate3(calls); 557 558 assertTrue(optimist.isOnAllowList(bob)); 559 assertEq(optimist.ownerOf(_getTokenId(bob)), bob); 560 assertEq(optimist.balanceOf(bob), 1); 561 } 562 }