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 }