Optimist.t.sol 28.2 KB
Newer Older
1
// SPDX-License-Identifier: MIT
2
pragma solidity >=0.6.2 <0.9.0;
Will Cory's avatar
Will Cory committed
3

4
// Testing utilities
Will Cory's avatar
Will Cory committed
5
import { Test } from "forge-std/Test.sol";
6 7 8 9
import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol";
import { Optimist } from "src/periphery/op-nft/Optimist.sol";
import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol";
import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol";
10
import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol";
Will Cory's avatar
Will Cory committed
11 12 13
import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

14 15 16 17 18 19 20 21 22 23 24 25
interface IMulticall3 {
    struct Call3 {
        address target;
        bool allowFailure;
        bytes callData;
    }

    struct Result {
        bool success;
        bytes returnData;
    }

26
    function aggregate3(Call3[] calldata calls) external payable returns (Result[] memory returnData);
27 28
}

29
library Multicall {
30 31 32
    bytes internal constant code =
        hex"6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033";
    address internal constant addr = 0xcA11bde05977b3631167028862bE2a173976CA11;
33 34
}

Will Cory's avatar
Will Cory committed
35 36 37
contract Optimist_Initializer is Test {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Initialized(uint8);
38
    event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val);
Will Cory's avatar
Will Cory committed
39 40 41

    string constant name = "Optimist name";
    string constant symbol = "OPTIMISTSYMBOL";
42
    string constant base_uri = "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes";
Will Cory's avatar
Will Cory committed
43 44
    AttestationStation attestationStation;
    Optimist optimist;
45 46 47 48 49
    OptimistAllowlist optimistAllowlist;
    OptimistInviter optimistInviter;

    // Helps with EIP-712 signature generation
    OptimistInviterHelper optimistInviterHelper;
Will Cory's avatar
Will Cory committed
50

James Kim's avatar
James Kim committed
51
    // To test multicall for claiming and minting in one call
52
    IMulticall3 multicall3;
James Kim's avatar
James Kim committed
53

54 55
    address internal carol_baseURIAttestor;
    address internal alice_allowlistAttestor;
56
    address internal eve_inviteGranter;
57 58 59 60
    address internal ted_coinbaseAttestor;
    address internal bob;
    address internal sally;

61
    /// @notice BaseURI attestor sets the baseURI of the Optimist NFT.
James Kim's avatar
James Kim committed
62
    function _attestBaseURI(string memory _baseUri) internal {
63
        bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY();
64 65 66
        AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1);
        attestationData[0] =
            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 72 73
        attestationStation.attest(attestationData);
    }

74
    /// @notice Allowlist attestor creates an attestation for an address.
James Kim's avatar
James Kim committed
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 80
        attestationData[0] =
            AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") });
81 82 83 84 85 86 87 88 89 90

        vm.expectEmit(true, true, true, true, address(attestationStation));
        emit AttestationCreated(alice_allowlistAttestor, _about, attestationKey, bytes("true"));

        vm.prank(alice_allowlistAttestor);
        attestationStation.attest(attestationData);

        assertTrue(optimist.isOnAllowList(_about));
    }

91
    /// @notice Coinbase Quest attestor creates an attestation for an address.
James Kim's avatar
James Kim committed
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 97
        attestationData[0] =
            AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") });
98 99 100 101 102

        vm.expectEmit(true, true, true, true, address(attestationStation));
        emit AttestationCreated(ted_coinbaseAttestor, _about, attestationKey, bytes("true"));

        vm.prank(ted_coinbaseAttestor);
103
        attestationStation.attest(attestationData);
104 105 106 107

        assertTrue(optimist.isOnAllowList(_about));
    }

108
    /// @notice Issues invite, then claims it using the claimer's address.
James Kim's avatar
James Kim committed
109
    function _inviteAndClaim(address _about) internal {
110 111 112 113 114 115 116 117 118 119 120 121
        uint256 inviterPrivateKey = 0xbeefbeef;
        address inviter = vm.addr(inviterPrivateKey);

        address[] memory addresses = new address[](1);
        addresses[0] = inviter;

        vm.prank(eve_inviteGranter);

        // grant invites to Inviter;
        optimistInviter.setInviteCounts(addresses, 3);

        // issue a new invite
122 123
        OptimistInviter.ClaimableInvite memory claimableInvite =
            optimistInviterHelper.getClaimableInviteWithNewNonce(inviter);
124 125 126

        // EIP-712 sign with Inviter's private key

127
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite));
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
        bytes memory signature = abi.encodePacked(r, s, v);

        bytes32 hashedCommit = keccak256(abi.encode(_about, signature));

        // commit the invite
        vm.prank(_about);
        optimistInviter.commitInvite(hashedCommit);

        // wait minimum commitment period
        vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);

        // reveal and claim the invite
        optimistInviter.claimInvite(_about, claimableInvite, signature);

        assertTrue(optimist.isOnAllowList(_about));
143 144
    }

145
    /// @notice Mocks the allowlistAttestor to always return true for a given address.
James Kim's avatar
James Kim committed
146 147 148 149 150 151
    function _mockAllowlistTrueFor(address _claimer) internal {
        vm.mockCall(
            address(optimistAllowlist),
            abi.encodeWithSelector(OptimistAllowlist.isAllowedToMint.selector, _claimer),
            abi.encode(true)
        );
James Kim's avatar
James Kim committed
152 153

        assertTrue(optimist.isOnAllowList(_claimer));
James Kim's avatar
James Kim committed
154 155
    }

156
    /// @notice Returns address as uint256.
James Kim's avatar
James Kim committed
157 158 159 160
    function _getTokenId(address _owner) internal pure returns (uint256) {
        return uint256(uint160(address(_owner)));
    }

161
    function setUp() public {
162 163
        carol_baseURIAttestor = makeAddr("carol_baseURIAttestor");
        alice_allowlistAttestor = makeAddr("alice_allowlistAttestor");
164
        eve_inviteGranter = makeAddr("eve_inviteGranter");
165 166 167
        ted_coinbaseAttestor = makeAddr("ted_coinbaseAttestor");
        bob = makeAddr("bob");
        sally = makeAddr("sally");
Will Cory's avatar
Will Cory committed
168 169 170 171 172 173 174
        _initializeContracts();
    }

    function _initializeContracts() internal {
        attestationStation = new AttestationStation();
        vm.expectEmit(true, true, false, false);
        emit Initialized(1);
175

176 177 178 179 180 181 182
        optimistInviter = new OptimistInviter({
            _inviteGranter: eve_inviteGranter,
            _attestationStation: attestationStation
        });

        optimistInviter.initialize("OptimistInviter");

James Kim's avatar
James Kim committed
183
        // Initialize the helper which helps sign EIP-712 signatures
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
        optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter");

        optimistAllowlist = new OptimistAllowlist({
            _attestationStation: attestationStation,
            _allowlistAttestor: alice_allowlistAttestor,
            _coinbaseQuestAttestor: ted_coinbaseAttestor,
            _optimistInviter: address(optimistInviter)
        });

        optimist = new Optimist({
            _name: name,
            _symbol: symbol,
            _baseURIAttestor: carol_baseURIAttestor,
            _attestationStation: attestationStation,
            _optimistAllowlist: optimistAllowlist
        });
James Kim's avatar
James Kim committed
200

201 202
        multicall3 = IMulticall3(Multicall.addr);
        vm.etch(Multicall.addr, Multicall.code);
203
    }
Will Cory's avatar
Will Cory committed
204 205 206
}

contract OptimistTest is Optimist_Initializer {
207
    /// @notice Check that constructor and initializer parameters are correctly set.
208
    function test_initialize_succeeds() external {
Will Cory's avatar
Will Cory committed
209 210 211 212 213 214
        // expect name to be set
        assertEq(optimist.name(), name);
        // expect symbol to be set
        assertEq(optimist.symbol(), symbol);
        // expect attestationStation to be set
        assertEq(address(optimist.ATTESTATION_STATION()), address(attestationStation));
215
        assertEq(optimist.BASE_URI_ATTESTOR(), carol_baseURIAttestor);
Will Cory's avatar
Will Cory committed
216 217
    }

218 219
    /// @notice Bob should be able to mint an NFT if he is allowlisted
    ///         by the allowlistAttestor and has a balance of 0.
220
    function test_mint_afterAllowlistAttestation_succeeds() external {
Will Cory's avatar
Will Cory committed
221 222 223
        // bob should start with 0 balance
        assertEq(optimist.balanceOf(bob), 0);

224
        // allowlist bob
James Kim's avatar
James Kim committed
225
        _attestAllowlist(bob);
Will Cory's avatar
Will Cory committed
226

227 228 229 230 231 232 233
        assertTrue(optimistAllowlist.isAllowedToMint(bob));

        // Check that the OptimistAllowlist is checked
        bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
        vm.expectCall(address(optimistAllowlist), data);

        // mint an NFT and expect mint transfer event to be emitted
Will Cory's avatar
Will Cory committed
234
        vm.expectEmit(true, true, true, true);
235 236 237
        emit Transfer(address(0), bob, _getTokenId(bob));
        vm.prank(bob);
        optimist.mint(bob);
Will Cory's avatar
Will Cory committed
238

239 240 241 242 243
        // expect the NFT to be owned by bob
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
        assertEq(optimist.balanceOf(bob), 1);
    }

244 245
    /// @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter
    ///         and has a balance of 0.
246 247 248 249 250
    function test_mint_afterInviteClaimed_succeeds() external {
        // bob should start with 0 balance
        assertEq(optimist.balanceOf(bob), 0);

        // bob claims an invite
James Kim's avatar
James Kim committed
251
        _inviteAndClaim(bob);
252 253 254 255 256 257 258 259 260 261

        assertTrue(optimistAllowlist.isAllowedToMint(bob));

        // Check that the OptimistAllowlist is checked
        bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
        vm.expectCall(address(optimistAllowlist), data);

        // mint an NFT and expect mint transfer event to be emitted
        vm.expectEmit(true, true, true, true);
        emit Transfer(address(0), bob, _getTokenId(bob));
Will Cory's avatar
Will Cory committed
262 263
        vm.prank(bob);
        optimist.mint(bob);
264

Will Cory's avatar
Will Cory committed
265
        // expect the NFT to be owned by bob
266
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
Will Cory's avatar
Will Cory committed
267 268 269
        assertEq(optimist.balanceOf(bob), 1);
    }

270 271
    /// @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest
    ///         attestor and has a balance of 0.
272 273 274 275 276
    function test_mint_afterCoinbaseQuestAttestation_succeeds() external {
        // bob should start with 0 balance
        assertEq(optimist.balanceOf(bob), 0);

        // bob receives attestation from Coinbase Quest attestor
James Kim's avatar
James Kim committed
277
        _attestCoinbaseQuest(bob);
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

        assertTrue(optimistAllowlist.isAllowedToMint(bob));

        // Check that the OptimistAllowlist is checked
        bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
        vm.expectCall(address(optimistAllowlist), data);

        // mint an NFT and expect mint transfer event to be emitted
        vm.expectEmit(true, true, true, true);
        emit Transfer(address(0), bob, _getTokenId(bob));
        vm.prank(bob);
        optimist.mint(bob);

        // expect the NFT to be owned by bob
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
        assertEq(optimist.balanceOf(bob), 1);
    }

296
    /// @notice Multiple valid attestations should allow Bob to mint.
James Kim's avatar
James Kim committed
297 298 299 300 301
    function test_mint_afterMultipleAttestations_succeeds() external {
        // bob should start with 0 balance
        assertEq(optimist.balanceOf(bob), 0);

        // bob receives attestation from Coinbase Quest attestor
James Kim's avatar
James Kim committed
302
        _attestCoinbaseQuest(bob);
James Kim's avatar
James Kim committed
303 304

        // allowlist bob
James Kim's avatar
James Kim committed
305
        _attestAllowlist(bob);
James Kim's avatar
James Kim committed
306 307

        // bob claims an invite
James Kim's avatar
James Kim committed
308
        _inviteAndClaim(bob);
James Kim's avatar
James Kim committed
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326

        assertTrue(optimistAllowlist.isAllowedToMint(bob));

        // Check that the OptimistAllowlist is checked
        bytes memory data = abi.encodeWithSelector(optimistAllowlist.isAllowedToMint.selector, bob);
        vm.expectCall(address(optimistAllowlist), data);

        // mint an NFT and expect mint transfer event to be emitted
        vm.expectEmit(true, true, true, true);
        emit Transfer(address(0), bob, _getTokenId(bob));
        vm.prank(bob);
        optimist.mint(bob);

        // expect the NFT to be owned by bob
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
        assertEq(optimist.balanceOf(bob), 1);
    }

327
    /// @notice Sally should be able to mint a token on behalf of bob.
328
    function test_mint_secondaryMinter_succeeds() external {
James Kim's avatar
James Kim committed
329
        _mockAllowlistTrueFor(bob);
Will Cory's avatar
Will Cory committed
330 331

        vm.expectEmit(true, true, true, true);
James Kim's avatar
James Kim committed
332
        emit Transfer(address(0), bob, _getTokenId(bob));
Will Cory's avatar
Will Cory committed
333 334 335 336 337 338

        // mint as sally instead of bob
        vm.prank(sally);
        optimist.mint(bob);

        // expect the NFT to be owned by bob
339
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
Will Cory's avatar
Will Cory committed
340 341 342
        assertEq(optimist.balanceOf(bob), 1);
    }

343
    /// @notice Bob should not be able to mint an NFT if he is not allowlisted.
344
    function test_mint_forNonAllowlistedClaimer_reverts() external {
Will Cory's avatar
Will Cory committed
345 346 347 348 349
        vm.prank(bob);
        vm.expectRevert("Optimist: address is not on allowList");
        optimist.mint(bob);
    }

350
    /// @notice Bob's tx should revert if he already minted.
351
    function test_mint_forAlreadyMintedClaimer_reverts() external {
James Kim's avatar
James Kim committed
352
        _attestAllowlist(bob);
Will Cory's avatar
Will Cory committed
353 354 355 356 357

        // mint initial nft with bob
        vm.prank(bob);
        optimist.mint(bob);
        // expect the NFT to be owned by bob
358
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
Will Cory's avatar
Will Cory committed
359 360 361 362 363 364 365
        assertEq(optimist.balanceOf(bob), 1);

        // attempt to mint again
        vm.expectRevert("ERC721: token already minted");
        optimist.mint(bob);
    }

366
    /// @notice The baseURI should be set by attestation station by the baseURIAttestor.
367
    function test_baseURI_returnsCorrectBaseURI_succeeds() external {
James Kim's avatar
James Kim committed
368
        _attestBaseURI(base_uri);
Will Cory's avatar
Will Cory committed
369 370 371

        bytes memory data = abi.encodeWithSelector(
            attestationStation.attestations.selector,
372
            carol_baseURIAttestor,
Will Cory's avatar
Will Cory committed
373
            address(optimist),
374
            optimist.BASE_URI_ATTESTATION_KEY()
Will Cory's avatar
Will Cory committed
375 376
        );
        vm.expectCall(address(attestationStation), data);
377
        vm.prank(carol_baseURIAttestor);
Will Cory's avatar
Will Cory committed
378 379 380 381 382

        // assert baseURI is set
        assertEq(optimist.baseURI(), base_uri);
    }

383
    /// @notice tokenURI should return the token uri for a minted token.
384
    function test_tokenURI_returnsCorrectTokenURI_succeeds() external {
Will Cory's avatar
Will Cory committed
385
        // we are using true but it can be any non empty value
James Kim's avatar
James Kim committed
386
        _attestBaseURI(base_uri);
Will Cory's avatar
Will Cory committed
387 388

        // mint an NFT
James Kim's avatar
James Kim committed
389
        _mockAllowlistTrueFor(bob);
Will Cory's avatar
Will Cory committed
390 391 392 393 394 395
        vm.prank(bob);
        optimist.mint(bob);

        // assert tokenURI is set
        assertEq(optimist.baseURI(), base_uri);
        assertEq(
396 397
            optimist.tokenURI(_getTokenId(bob)),
            "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e.json"
Will Cory's avatar
Will Cory committed
398 399 400
        );
    }

401
    /// @notice Should return the token id of the owner.
402
    function test_tokenIdOfAddress_returnsOwnerID_succeeds() external {
Will Cory's avatar
Will Cory committed
403 404 405
        uint256 willTokenId = 1024;
        address will = address(1024);

James Kim's avatar
James Kim committed
406
        _mockAllowlistTrueFor(will);
Will Cory's avatar
Will Cory committed
407 408 409 410 411 412

        optimist.mint(will);

        assertEq(optimist.tokenIdOfAddress(will), willTokenId);
    }

413
    /// @notice transferFrom should revert since Optimist is a SBT.
414
    function test_transferFrom_soulbound_reverts() external {
James Kim's avatar
James Kim committed
415
        _mockAllowlistTrueFor(bob);
Will Cory's avatar
Will Cory committed
416 417 418 419 420 421 422 423

        // mint as bob
        vm.prank(bob);
        optimist.mint(bob);

        // attempt to transfer to sally
        vm.expectRevert(bytes("Optimist: soul bound token"));
        vm.prank(bob);
424
        optimist.transferFrom(bob, sally, _getTokenId(bob));
Will Cory's avatar
Will Cory committed
425 426 427 428

        // attempt to transfer to sally
        vm.expectRevert(bytes("Optimist: soul bound token"));
        vm.prank(bob);
429
        optimist.safeTransferFrom(bob, sally, _getTokenId(bob));
Will Cory's avatar
Will Cory committed
430 431 432
        // attempt to transfer to sally
        vm.expectRevert(bytes("Optimist: soul bound token"));
        vm.prank(bob);
433
        optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x"));
Will Cory's avatar
Will Cory committed
434 435
    }

436
    /// @notice approve should revert since Optimist is a SBT.
437
    function test_approve_soulbound_reverts() external {
James Kim's avatar
James Kim committed
438
        _mockAllowlistTrueFor(bob);
Will Cory's avatar
Will Cory committed
439 440 441 442 443 444 445 446

        // mint as bob
        vm.prank(bob);
        optimist.mint(bob);

        // attempt to approve sally
        vm.prank(bob);
        vm.expectRevert("Optimist: soul bound token");
447
        optimist.approve(address(attestationStation), _getTokenId(bob));
Will Cory's avatar
Will Cory committed
448

449
        assertEq(optimist.getApproved(_getTokenId(bob)), address(0));
Will Cory's avatar
Will Cory committed
450 451
    }

452
    /// @notice setApprovalForAll should revert since Optimist is a SBT.
453
    function test_setApprovalForAll_soulbound_reverts() external {
James Kim's avatar
James Kim committed
454 455 456 457 458 459 460 461 462 463 464 465
        _mockAllowlistTrueFor(bob);

        // mint as bob
        vm.prank(bob);
        optimist.mint(bob);
        vm.prank(alice_allowlistAttestor);
        vm.expectRevert(bytes("Optimist: soul bound token"));
        optimist.setApprovalForAll(alice_allowlistAttestor, true);

        // expect approval amount to stil be 0
        assertEq(optimist.getApproved(_getTokenId(bob)), address(0));
        // isApprovedForAll should return false
466
        assertEq(optimist.isApprovedForAll(alice_allowlistAttestor, alice_allowlistAttestor), false);
James Kim's avatar
James Kim committed
467 468
    }

469
    /// @notice Only owner should be able to burn token.
470
    function test_burn_byOwner_succeeds() external {
James Kim's avatar
James Kim committed
471
        _mockAllowlistTrueFor(bob);
Will Cory's avatar
Will Cory committed
472 473 474 475 476 477 478

        // mint as bob
        vm.prank(bob);
        optimist.mint(bob);

        // burn as bob
        vm.prank(bob);
479
        optimist.burn(_getTokenId(bob));
Will Cory's avatar
Will Cory committed
480 481 482 483 484

        // expect bob to have no balance now
        assertEq(optimist.balanceOf(bob), 0);
    }

485
    /// @notice Non-owner attempting to burn token should revert.
James Kim's avatar
James Kim committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
    function test_burn_byNonOwner_reverts() external {
        _mockAllowlistTrueFor(bob);

        // mint as bob
        vm.prank(bob);
        optimist.mint(bob);

        vm.expectRevert("ERC721: caller is not token owner nor approved");
        // burn as Sally
        vm.prank(sally);
        optimist.burn(_getTokenId(bob));

        // expect bob to have still have the token
        assertEq(optimist.balanceOf(bob), 1);
    }

502
    /// @notice Should support ERC-721 interface.
503
    function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external {
Will Cory's avatar
Will Cory committed
504
        bytes4 iface721 = type(IERC721).interfaceId;
James Kim's avatar
James Kim committed
505
        // check that it supports ERC-721 interface
Will Cory's avatar
Will Cory committed
506 507
        assertEq(optimist.supportsInterface(iface721), true);
    }
James Kim's avatar
James Kim committed
508

509 510 511
    /// @notice Checking that multi-call using the invite & claim flow works correctly, since the
    ///         frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite
    ///         and Optimist.mint will be batched
James Kim's avatar
James Kim committed
512 513 514 515 516 517 518 519 520 521 522 523 524
    function test_multicall_batchingClaimAndMint_succeeds() external {
        uint256 inviterPrivateKey = 0xbeefbeef;
        address inviter = vm.addr(inviterPrivateKey);

        address[] memory addresses = new address[](1);
        addresses[0] = inviter;

        vm.prank(eve_inviteGranter);

        // grant invites to Inviter;
        optimistInviter.setInviteCounts(addresses, 3);

        // issue a new invite
525 526
        OptimistInviter.ClaimableInvite memory claimableInvite =
            optimistInviterHelper.getClaimableInviteWithNewNonce(inviter);
James Kim's avatar
James Kim committed
527 528 529

        // EIP-712 sign with Inviter's private key

530
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite));
James Kim's avatar
James Kim committed
531 532 533 534 535 536 537 538 539 540 541
        bytes memory signature = abi.encodePacked(r, s, v);

        bytes32 hashedCommit = keccak256(abi.encode(bob, signature));

        // commit the invite
        vm.prank(bob);
        optimistInviter.commitInvite(hashedCommit);

        // wait minimum commitment period
        vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp);

542
        IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2);
James Kim's avatar
James Kim committed
543 544

        // First call is to claim the invite, receiving the attestation
545
        calls[0] = IMulticall3.Call3({
James Kim's avatar
James Kim committed
546
            target: address(optimistInviter),
547
            callData: abi.encodeWithSelector(optimistInviter.claimInvite.selector, bob, claimableInvite, signature),
James Kim's avatar
James Kim committed
548 549 550 551
            allowFailure: false
        });

        // Second call is to mint the Optimist NFT
552
        calls[1] = IMulticall3.Call3({
James Kim's avatar
James Kim committed
553 554 555 556 557 558 559 560 561 562 563
            target: address(optimist),
            callData: abi.encodeWithSelector(optimist.mint.selector, bob),
            allowFailure: false
        });

        multicall3.aggregate3(calls);

        assertTrue(optimist.isOnAllowList(bob));
        assertEq(optimist.ownerOf(_getTokenId(bob)), bob);
        assertEq(optimist.balanceOf(bob), 1);
    }
Will Cory's avatar
Will Cory committed
564
}