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  }