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  }