github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test } from "forge-std/Test.sol";
     5  import { Faucet } from "src/periphery/faucet/Faucet.sol";
     6  import { AdminFaucetAuthModule } from "src/periphery/faucet/authmodules/AdminFaucetAuthModule.sol";
     7  import { FaucetHelper } from "test/mocks/FaucetHelper.sol";
     8  
     9  contract Faucet_Initializer is Test {
    10      event Drip(string indexed authModule, bytes32 indexed userId, uint256 amount, address indexed recipient);
    11  
    12      address internal faucetContractAdmin;
    13      address internal faucetAuthAdmin;
    14      address internal nonAdmin;
    15      address internal fundsReceiver;
    16      uint256 internal faucetAuthAdminKey;
    17      uint256 internal nonAdminKey;
    18      uint256 internal startingTimestamp = 1000;
    19  
    20      Faucet faucet;
    21      AdminFaucetAuthModule optimistNftFam;
    22      string internal optimistNftFamName = "OptimistNftFam";
    23      string internal optimistNftFamVersion = "1";
    24      AdminFaucetAuthModule githubFam;
    25      string internal githubFamName = "GithubFam";
    26      string internal githubFamVersion = "1";
    27  
    28      FaucetHelper faucetHelper;
    29  
    30      function setUp() public {
    31          vm.warp(startingTimestamp);
    32          faucetContractAdmin = makeAddr("faucetContractAdmin");
    33          fundsReceiver = makeAddr("fundsReceiver");
    34  
    35          faucetAuthAdminKey = 0xB0B0B0B0;
    36          faucetAuthAdmin = vm.addr(faucetAuthAdminKey);
    37  
    38          nonAdminKey = 0xC0C0C0C0;
    39          nonAdmin = vm.addr(nonAdminKey);
    40  
    41          _initializeContracts();
    42      }
    43  
    44      /// @notice Instantiates a Faucet.
    45      function _initializeContracts() internal {
    46          faucet = new Faucet(faucetContractAdmin);
    47  
    48          // Fill faucet with ether.
    49          vm.deal(address(faucet), 10 ether);
    50          vm.deal(address(faucetContractAdmin), 5 ether);
    51          vm.deal(address(nonAdmin), 5 ether);
    52  
    53          optimistNftFam = new AdminFaucetAuthModule(faucetAuthAdmin, optimistNftFamName, optimistNftFamVersion);
    54          githubFam = new AdminFaucetAuthModule(faucetAuthAdmin, githubFamName, githubFamVersion);
    55  
    56          faucetHelper = new FaucetHelper();
    57      }
    58  
    59      function _enableFaucetAuthModules() internal {
    60          vm.startPrank(faucetContractAdmin);
    61          faucet.configure(optimistNftFam, Faucet.ModuleConfig("OptimistNftModule", true, 1 days, 1 ether));
    62          faucet.configure(githubFam, Faucet.ModuleConfig("GithubModule", true, 1 days, 0.05 ether));
    63          vm.stopPrank();
    64      }
    65  
    66      /// @notice Get signature as a bytes blob.
    67      function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) {
    68          (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest);
    69  
    70          bytes memory signature = abi.encodePacked(r, s, v);
    71          return signature;
    72      }
    73  
    74      /// @notice Signs a proof with the given private key and returns the signature using
    75      ///         the given EIP712 domain separator. This assumes that the issuer's address is the
    76      ///         corresponding public key to _issuerPrivateKey.
    77      function issueProofWithEIP712Domain(
    78          uint256 _issuerPrivateKey,
    79          bytes memory _eip712Name,
    80          bytes memory _contractVersion,
    81          uint256 _eip712Chainid,
    82          address _eip712VerifyingContract,
    83          address recipient,
    84          bytes32 id,
    85          bytes32 nonce
    86      )
    87          internal
    88          view
    89          returns (bytes memory)
    90      {
    91          AdminFaucetAuthModule.Proof memory proof = AdminFaucetAuthModule.Proof(recipient, nonce, id);
    92          return _getSignature(
    93              _issuerPrivateKey,
    94              faucetHelper.getDigestWithEIP712Domain(
    95                  proof, _eip712Name, _contractVersion, _eip712Chainid, _eip712VerifyingContract
    96              )
    97          );
    98      }
    99  }
   100  
   101  contract FaucetTest is Faucet_Initializer {
   102      function test_initialize_succeeds() external {
   103          assertEq(faucet.ADMIN(), faucetContractAdmin);
   104      }
   105  
   106      function test_authAdmin_drip_succeeds() external {
   107          _enableFaucetAuthModules();
   108          bytes32 nonce = faucetHelper.consumeNonce();
   109          bytes memory signature = issueProofWithEIP712Domain(
   110              faucetAuthAdminKey,
   111              bytes(optimistNftFamName),
   112              bytes(optimistNftFamVersion),
   113              block.chainid,
   114              address(optimistNftFam),
   115              fundsReceiver,
   116              keccak256(abi.encodePacked(fundsReceiver)),
   117              nonce
   118          );
   119  
   120          vm.prank(nonAdmin);
   121          faucet.drip(
   122              Faucet.DripParameters(payable(fundsReceiver), nonce),
   123              Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   124          );
   125      }
   126  
   127      function test_nonAdmin_drip_fails() external {
   128          _enableFaucetAuthModules();
   129          bytes32 nonce = faucetHelper.consumeNonce();
   130          bytes memory signature = issueProofWithEIP712Domain(
   131              nonAdminKey,
   132              bytes(optimistNftFamName),
   133              bytes(optimistNftFamVersion),
   134              block.chainid,
   135              address(optimistNftFam),
   136              fundsReceiver,
   137              keccak256(abi.encodePacked(fundsReceiver)),
   138              nonce
   139          );
   140  
   141          vm.prank(nonAdmin);
   142          vm.expectRevert("Faucet: drip parameters could not be verified by security module");
   143          faucet.drip(
   144              Faucet.DripParameters(payable(fundsReceiver), nonce),
   145              Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   146          );
   147      }
   148  
   149      function test_drip_optimistNftSendsCorrectAmount_succeeds() external {
   150          _enableFaucetAuthModules();
   151          bytes32 nonce = faucetHelper.consumeNonce();
   152          bytes memory signature = issueProofWithEIP712Domain(
   153              faucetAuthAdminKey,
   154              bytes(optimistNftFamName),
   155              bytes(optimistNftFamVersion),
   156              block.chainid,
   157              address(optimistNftFam),
   158              fundsReceiver,
   159              keccak256(abi.encodePacked(fundsReceiver)),
   160              nonce
   161          );
   162  
   163          uint256 recipientBalanceBefore = address(fundsReceiver).balance;
   164          vm.prank(nonAdmin);
   165          faucet.drip(
   166              Faucet.DripParameters(payable(fundsReceiver), nonce),
   167              Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   168          );
   169          uint256 recipientBalanceAfter = address(fundsReceiver).balance;
   170          assertEq(recipientBalanceAfter - recipientBalanceBefore, 1 ether, "expect increase of 1 ether");
   171      }
   172  
   173      function test_drip_githubSendsCorrectAmount_succeeds() external {
   174          _enableFaucetAuthModules();
   175          bytes32 nonce = faucetHelper.consumeNonce();
   176          bytes memory signature = issueProofWithEIP712Domain(
   177              faucetAuthAdminKey,
   178              bytes(githubFamName),
   179              bytes(githubFamVersion),
   180              block.chainid,
   181              address(githubFam),
   182              fundsReceiver,
   183              keccak256(abi.encodePacked(fundsReceiver)),
   184              nonce
   185          );
   186  
   187          uint256 recipientBalanceBefore = address(fundsReceiver).balance;
   188          vm.prank(nonAdmin);
   189          faucet.drip(
   190              Faucet.DripParameters(payable(fundsReceiver), nonce),
   191              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   192          );
   193          uint256 recipientBalanceAfter = address(fundsReceiver).balance;
   194          assertEq(recipientBalanceAfter - recipientBalanceBefore, 0.05 ether, "expect increase of .05 ether");
   195      }
   196  
   197      function test_drip_emitsEvent_succeeds() external {
   198          _enableFaucetAuthModules();
   199          bytes32 nonce = faucetHelper.consumeNonce();
   200          bytes memory signature = issueProofWithEIP712Domain(
   201              faucetAuthAdminKey,
   202              bytes(githubFamName),
   203              bytes(githubFamVersion),
   204              block.chainid,
   205              address(githubFam),
   206              fundsReceiver,
   207              keccak256(abi.encodePacked(fundsReceiver)),
   208              nonce
   209          );
   210  
   211          vm.expectEmit(true, true, true, true, address(faucet));
   212          emit Drip("GithubModule", keccak256(abi.encodePacked(fundsReceiver)), 0.05 ether, fundsReceiver);
   213  
   214          vm.prank(nonAdmin);
   215          faucet.drip(
   216              Faucet.DripParameters(payable(fundsReceiver), nonce),
   217              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   218          );
   219      }
   220  
   221      function test_drip_disabledModule_reverts() external {
   222          _enableFaucetAuthModules();
   223          bytes32 nonce = faucetHelper.consumeNonce();
   224          bytes memory signature = issueProofWithEIP712Domain(
   225              faucetAuthAdminKey,
   226              bytes(githubFamName),
   227              bytes(githubFamVersion),
   228              block.chainid,
   229              address(githubFam),
   230              fundsReceiver,
   231              keccak256(abi.encodePacked(fundsReceiver)),
   232              nonce
   233          );
   234  
   235          vm.startPrank(faucetContractAdmin);
   236          faucet.drip(
   237              Faucet.DripParameters(payable(fundsReceiver), nonce),
   238              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   239          );
   240  
   241          faucet.configure(githubFam, Faucet.ModuleConfig("GithubModule", false, 1 days, 0.05 ether));
   242  
   243          vm.expectRevert("Faucet: provided auth module is not supported by this faucet");
   244          faucet.drip(
   245              Faucet.DripParameters(payable(fundsReceiver), nonce),
   246              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   247          );
   248          vm.stopPrank();
   249      }
   250  
   251      function test_drip_preventsReplayAttacks_succeeds() external {
   252          _enableFaucetAuthModules();
   253          bytes32 nonce = faucetHelper.consumeNonce();
   254          bytes memory signature = issueProofWithEIP712Domain(
   255              faucetAuthAdminKey,
   256              bytes(githubFamName),
   257              bytes(githubFamVersion),
   258              block.chainid,
   259              address(githubFam),
   260              fundsReceiver,
   261              keccak256(abi.encodePacked(fundsReceiver)),
   262              nonce
   263          );
   264  
   265          vm.startPrank(faucetContractAdmin);
   266          faucet.drip(
   267              Faucet.DripParameters(payable(fundsReceiver), nonce),
   268              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   269          );
   270  
   271          vm.expectRevert("Faucet: nonce has already been used");
   272          faucet.drip(
   273              Faucet.DripParameters(payable(fundsReceiver), nonce),
   274              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature)
   275          );
   276          vm.stopPrank();
   277      }
   278  
   279      function test_drip_beforeTimeout_reverts() external {
   280          _enableFaucetAuthModules();
   281          bytes32 nonce0 = faucetHelper.consumeNonce();
   282          bytes memory signature0 = issueProofWithEIP712Domain(
   283              faucetAuthAdminKey,
   284              bytes(githubFamName),
   285              bytes(githubFamVersion),
   286              block.chainid,
   287              address(githubFam),
   288              fundsReceiver,
   289              keccak256(abi.encodePacked(fundsReceiver)),
   290              nonce0
   291          );
   292  
   293          vm.startPrank(faucetContractAdmin);
   294          faucet.drip(
   295              Faucet.DripParameters(payable(fundsReceiver), nonce0),
   296              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0)
   297          );
   298  
   299          bytes32 nonce1 = faucetHelper.consumeNonce();
   300          bytes memory signature1 = issueProofWithEIP712Domain(
   301              faucetAuthAdminKey,
   302              bytes(githubFamName),
   303              bytes(githubFamVersion),
   304              block.chainid,
   305              address(githubFam),
   306              fundsReceiver,
   307              keccak256(abi.encodePacked(fundsReceiver)),
   308              nonce1
   309          );
   310  
   311          vm.expectRevert("Faucet: auth cannot be used yet because timeout has not elapsed");
   312          faucet.drip(
   313              Faucet.DripParameters(payable(fundsReceiver), nonce1),
   314              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1)
   315          );
   316          vm.stopPrank();
   317      }
   318  
   319      function test_drip_afterTimeout_succeeds() external {
   320          _enableFaucetAuthModules();
   321          bytes32 nonce0 = faucetHelper.consumeNonce();
   322          bytes memory signature0 = issueProofWithEIP712Domain(
   323              faucetAuthAdminKey,
   324              bytes(githubFamName),
   325              bytes(githubFamVersion),
   326              block.chainid,
   327              address(githubFam),
   328              fundsReceiver,
   329              keccak256(abi.encodePacked(fundsReceiver)),
   330              nonce0
   331          );
   332  
   333          vm.startPrank(faucetContractAdmin);
   334          faucet.drip(
   335              Faucet.DripParameters(payable(fundsReceiver), nonce0),
   336              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0)
   337          );
   338  
   339          bytes32 nonce1 = faucetHelper.consumeNonce();
   340          bytes memory signature1 = issueProofWithEIP712Domain(
   341              faucetAuthAdminKey,
   342              bytes(githubFamName),
   343              bytes(githubFamVersion),
   344              block.chainid,
   345              address(githubFam),
   346              fundsReceiver,
   347              keccak256(abi.encodePacked(fundsReceiver)),
   348              nonce1
   349          );
   350  
   351          vm.warp(startingTimestamp + 1 days + 1 seconds);
   352          faucet.drip(
   353              Faucet.DripParameters(payable(fundsReceiver), nonce1),
   354              Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1)
   355          );
   356          vm.stopPrank();
   357      }
   358  
   359      function test_withdraw_succeeds() external {
   360          vm.startPrank(faucetContractAdmin);
   361          uint256 recipientBalanceBefore = address(fundsReceiver).balance;
   362  
   363          faucet.withdraw(payable(fundsReceiver), 2 ether);
   364  
   365          uint256 recipientBalanceAfter = address(fundsReceiver).balance;
   366          assertEq(recipientBalanceAfter - recipientBalanceBefore, 2 ether, "expect increase of 2 ether");
   367          vm.stopPrank();
   368      }
   369  
   370      function test_withdraw_nonAdmin_reverts() external {
   371          vm.prank(nonAdmin);
   372          vm.expectRevert("Faucet: function can only be called by admin");
   373          faucet.withdraw(payable(fundsReceiver), 2 ether);
   374      }
   375  
   376      function test_receive_succeeds() external {
   377          uint256 faucetBalanceBefore = address(faucet).balance;
   378  
   379          vm.prank(nonAdmin);
   380          (bool success,) = address(faucet).call{ value: 1 ether }("");
   381          assertTrue(success);
   382  
   383          uint256 faucetBalanceAfter = address(faucet).balance;
   384          assertEq(faucetBalanceAfter - faucetBalanceBefore, 1 ether, "expect increase of 1 ether");
   385      }
   386  }