diff --git a/.changeset/witty-pants-move.md b/.changeset/witty-pants-move.md new file mode 100644 index 000000000000..aa85cb679e4e --- /dev/null +++ b/.changeset/witty-pants-move.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/contracts-bedrock': patch +--- + +Add data field to faucet contract drip parameters diff --git a/packages/contracts-bedrock/snapshots/abi/AdminFaucetAuthModule.json b/packages/contracts-bedrock/snapshots/abi/AdminFaucetAuthModule.json index 7c2112223259..fc0dbd595b1f 100644 --- a/packages/contracts-bedrock/snapshots/abi/AdminFaucetAuthModule.json +++ b/packages/contracts-bedrock/snapshots/abi/AdminFaucetAuthModule.json @@ -55,10 +55,20 @@ "name": "recipient", "type": "address" }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, { "internalType": "bytes32", "name": "nonce", "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "gasLimit", + "type": "uint32" } ], "internalType": "struct Faucet.DripParameters", diff --git a/packages/contracts-bedrock/snapshots/abi/Faucet.json b/packages/contracts-bedrock/snapshots/abi/Faucet.json index f943faed8283..79cdc8315398 100644 --- a/packages/contracts-bedrock/snapshots/abi/Faucet.json +++ b/packages/contracts-bedrock/snapshots/abi/Faucet.json @@ -76,10 +76,20 @@ "name": "recipient", "type": "address" }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, { "internalType": "bytes32", "name": "nonce", "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "gasLimit", + "type": "uint32" } ], "internalType": "struct Faucet.DripParameters", diff --git a/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol b/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol index e23e6869c335..9266d00ed0f0 100644 --- a/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol +++ b/packages/contracts-bedrock/src/periphery/faucet/Faucet.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.15; import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol"; +import { SafeCall } from "../../libraries/SafeCall.sol"; /// @title SafeSend /// @notice Sends ETH to a recipient account without triggering any code. @@ -25,7 +26,9 @@ contract Faucet { /// @notice Parameters for a drip. struct DripParameters { address payable recipient; + bytes data; bytes32 nonce; + uint32 gasLimit; } /// @notice Parameters for authentication. @@ -113,14 +116,17 @@ contract Faucet { "Faucet: drip parameters could not be verified by security module" ); + // Verify recepient is not the faucet address. + require(_params.recipient != address(this), "Faucet: cannot drip to itself"); + // Set the next timestamp at which this auth id can be used. timeouts[_auth.module][_auth.id] = block.timestamp + config.ttl; // Mark the nonce as used. nonces[_auth.id][_params.nonce] = true; - // Execute a safe transfer of ETH to the recipient account. - new SafeSend{ value: config.amount }(_params.recipient); + // Execute transfer of ETH to the recipient account. + SafeCall.call(_params.recipient, _params.gasLimit, config.amount, _params.data); emit Drip(config.name, _auth.id, config.amount, _params.recipient); } diff --git a/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol b/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol index a888a367c4e7..a0ef75f28d55 100644 --- a/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol +++ b/packages/contracts-bedrock/test/periphery/faucet/Faucet.t.sol @@ -106,6 +106,8 @@ contract FaucetTest is Faucet_Initializer { function test_authAdmin_drip_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(optimistNftFamName), @@ -119,7 +121,7 @@ contract FaucetTest is Faucet_Initializer { vm.prank(nonAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); } @@ -127,6 +129,8 @@ contract FaucetTest is Faucet_Initializer { function test_nonAdmin_drip_fails() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( nonAdminKey, bytes(optimistNftFamName), @@ -141,7 +145,7 @@ contract FaucetTest is Faucet_Initializer { vm.prank(nonAdmin); vm.expectRevert("Faucet: drip parameters could not be verified by security module"); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); } @@ -149,6 +153,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_optimistNftSendsCorrectAmount_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(optimistNftFamName), @@ -163,7 +169,7 @@ contract FaucetTest is Faucet_Initializer { uint256 recipientBalanceBefore = address(fundsReceiver).balance; vm.prank(nonAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(optimistNftFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); uint256 recipientBalanceAfter = address(fundsReceiver).balance; @@ -173,6 +179,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_githubSendsCorrectAmount_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -187,7 +195,7 @@ contract FaucetTest is Faucet_Initializer { uint256 recipientBalanceBefore = address(fundsReceiver).balance; vm.prank(nonAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); uint256 recipientBalanceAfter = address(fundsReceiver).balance; @@ -197,6 +205,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_emitsEvent_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -213,7 +223,7 @@ contract FaucetTest is Faucet_Initializer { vm.prank(nonAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); } @@ -221,6 +231,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_disabledModule_reverts() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -234,7 +246,7 @@ contract FaucetTest is Faucet_Initializer { vm.startPrank(faucetContractAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); @@ -242,7 +254,7 @@ contract FaucetTest is Faucet_Initializer { vm.expectRevert("Faucet: provided auth module is not supported by this faucet"); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); vm.stopPrank(); @@ -251,6 +263,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_preventsReplayAttacks_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -264,13 +278,13 @@ contract FaucetTest is Faucet_Initializer { vm.startPrank(faucetContractAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); vm.expectRevert("Faucet: nonce has already been used"); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce), + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature) ); vm.stopPrank(); @@ -279,6 +293,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_beforeTimeout_reverts() external { _enableFaucetAuthModules(); bytes32 nonce0 = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature0 = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -292,7 +308,7 @@ contract FaucetTest is Faucet_Initializer { vm.startPrank(faucetContractAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce0), + Faucet.DripParameters(payable(fundsReceiver), data, nonce0, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0) ); @@ -310,7 +326,7 @@ contract FaucetTest is Faucet_Initializer { vm.expectRevert("Faucet: auth cannot be used yet because timeout has not elapsed"); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce1), + Faucet.DripParameters(payable(fundsReceiver), data, nonce1, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1) ); vm.stopPrank(); @@ -319,6 +335,8 @@ contract FaucetTest is Faucet_Initializer { function test_drip_afterTimeout_succeeds() external { _enableFaucetAuthModules(); bytes32 nonce0 = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; bytes memory signature0 = issueProofWithEIP712Domain( faucetAuthAdminKey, bytes(githubFamName), @@ -332,7 +350,7 @@ contract FaucetTest is Faucet_Initializer { vm.startPrank(faucetContractAdmin); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce0), + Faucet.DripParameters(payable(fundsReceiver), data, nonce0, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature0) ); @@ -350,7 +368,7 @@ contract FaucetTest is Faucet_Initializer { vm.warp(startingTimestamp + 1 days + 1 seconds); faucet.drip( - Faucet.DripParameters(payable(fundsReceiver), nonce1), + Faucet.DripParameters(payable(fundsReceiver), data, nonce1, gasLimit), Faucet.AuthParameters(githubFam, keccak256(abi.encodePacked(fundsReceiver)), signature1) ); vm.stopPrank(); diff --git a/packages/contracts-bedrock/test/periphery/faucet/authmodules/AdminFaucetAuthModule.t.sol b/packages/contracts-bedrock/test/periphery/faucet/authmodules/AdminFaucetAuthModule.t.sol index 93a10329b452..408c091135a2 100644 --- a/packages/contracts-bedrock/test/periphery/faucet/authmodules/AdminFaucetAuthModule.t.sol +++ b/packages/contracts-bedrock/test/periphery/faucet/authmodules/AdminFaucetAuthModule.t.sol @@ -81,6 +81,8 @@ contract AdminFaucetAuthModuleTest is Test { /// @notice Assert that verify returns true for valid proofs signed by admins. function test_adminProof_verify_succeeds() external { bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; address fundsReceiver = makeAddr("fundsReceiver"); bytes memory proof = issueProofWithEIP712Domain( adminKey, @@ -96,7 +98,9 @@ contract AdminFaucetAuthModuleTest is Test { vm.prank(nonAdmin); assertEq( adminFam.verify( - Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(fundsReceiver)), proof + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), + keccak256(abi.encodePacked(fundsReceiver)), + proof ), true ); @@ -105,6 +109,8 @@ contract AdminFaucetAuthModuleTest is Test { /// @notice Assert that verify returns false for proofs signed by nonadmins. function test_nonAdminProof_verify_succeeds() external { bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; address fundsReceiver = makeAddr("fundsReceiver"); bytes memory proof = issueProofWithEIP712Domain( nonAdminKey, @@ -120,7 +126,9 @@ contract AdminFaucetAuthModuleTest is Test { vm.prank(admin); assertEq( adminFam.verify( - Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(fundsReceiver)), proof + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), + keccak256(abi.encodePacked(fundsReceiver)), + proof ), false ); @@ -130,6 +138,8 @@ contract AdminFaucetAuthModuleTest is Test { /// than the id in the call to verify. function test_proofWithWrongId_verify_succeeds() external { bytes32 nonce = faucetHelper.consumeNonce(); + bytes memory data = "0x"; + uint32 gasLimit = 200000; address fundsReceiver = makeAddr("fundsReceiver"); address randomAddress = makeAddr("randomAddress"); bytes memory proof = issueProofWithEIP712Domain( @@ -146,7 +156,9 @@ contract AdminFaucetAuthModuleTest is Test { vm.prank(admin); assertEq( adminFam.verify( - Faucet.DripParameters(payable(fundsReceiver), nonce), keccak256(abi.encodePacked(randomAddress)), proof + Faucet.DripParameters(payable(fundsReceiver), data, nonce, gasLimit), + keccak256(abi.encodePacked(randomAddress)), + proof ), false );