Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
a5a55290
Unverified
Commit
a5a55290
authored
May 11, 2023
by
OptimismBot
Committed by
GitHub
May 11, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5595 from ethereum-optimism/harry/faucet-contracts
feat(contracts-periphery): create Faucet contract
parents
45fa82ec
b29868e8
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
968 additions
and
0 deletions
+968
-0
AdminFaucetAuthModule.t.sol
...phery/contracts/foundry-tests/AdminFaucetAuthModule.t.sol
+189
-0
Faucet.t.sol
.../contracts-periphery/contracts/foundry-tests/Faucet.t.sol
+425
-0
FaucetHelper.sol
...acts-periphery/contracts/testing/helpers/FaucetHelper.sol
+91
-0
Faucet.sol
...contracts-periphery/contracts/universal/faucet/Faucet.sol
+170
-0
AdminFaucetAuthModule.sol
...ts/universal/faucet/authmodules/AdminFaucetAuthModule.sol
+70
-0
IFaucetAuthModule.sol
...tracts/universal/faucet/authmodules/IFaucetAuthModule.sol
+23
-0
No files found.
packages/contracts-periphery/contracts/foundry-tests/AdminFaucetAuthModule.t.sol
0 → 100644
View file @
a5a55290
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { AdminFaucetAuthModule } from "../universal/faucet/authmodules/AdminFaucetAuthModule.sol";
import { Faucet } from "../universal/faucet/Faucet.sol";
import { FaucetHelper } from "../testing/helpers/FaucetHelper.sol";
/**
* @title AdminFaucetAuthModuleTest
* @notice Tests the AdminFaucetAuthModule contract.
*/
contract AdminFaucetAuthModuleTest is Test {
/**
* @notice The admin of the `AdminFaucetAuthModule` contract.
*/
address internal admin;
/**
* @notice Private key of the `admin`.
*/
uint256 internal adminKey;
/**
* @notice Not an admin of the `AdminFaucetAuthModule` contract.
*/
address internal nonAdmin;
/**
* @notice Private key of the `nonAdmin`.
*/
uint256 internal nonAdminKey;
/**
* @notice An instance of the `AdminFaucetAuthModule` contract.
*/
AdminFaucetAuthModule internal adminFam;
/**
* @notice An instance of the `FaucetHelper` contract.
*/
FaucetHelper internal faucetHelper;
string internal adminFamName = "AdminFAM";
string internal adminFamVersion = "1";
/**
* @notice Deploy the `AdminFaucetAuthModule` contract.
*/
function setUp() external {
adminKey = 0xB0B0B0B0;
admin = vm.addr(adminKey);
nonAdminKey = 0xC0C0C0C0;
nonAdmin = vm.addr(nonAdminKey);
adminFam = new AdminFaucetAuthModule(admin, adminFamName, adminFamVersion);
faucetHelper = new FaucetHelper();
}
/**
* @notice Get signature as a bytes blob.
*
*/
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
returns (bytes memory)
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest);
bytes memory signature = abi.encodePacked(r, s, v);
return signature;
}
/**
* @notice Signs a proof with the given private key and returns the signature using
* the given EIP712 domain separator. This assumes that the issuer's address is the
* corresponding public key to _issuerPrivateKey.
*/
function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey,
bytes memory _eip712Name,
bytes memory _contractVersion,
uint256 _eip712Chainid,
address _eip712VerifyingContract,
address recipient,
bytes memory id,
bytes32 nonce
) internal view returns (bytes memory) {
AdminFaucetAuthModule.Proof memory proof = AdminFaucetAuthModule.Proof(
recipient,
nonce,
id
);
return
_getSignature(
_issuerPrivateKey,
faucetHelper.getDigestWithEIP712Domain(
proof,
_eip712Name,
_contractVersion,
_eip712Chainid,
_eip712VerifyingContract
)
);
}
/**
* @notice assert that verify returns true for valid proofs signed by admins.
*/
function test_adminProof_verify_returnsTrue() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
bytes memory proof = issueProofWithEIP712Domain(
adminKey,
bytes(adminFamName),
bytes(adminFamVersion),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(nonAdmin);
assertEq(
adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce),
abi.encodePacked(fundsReceiver),
proof
),
true
);
}
/**
* @notice assert that verify returns false for proofs signed by nonadmins.
*/
function test_nonAdminProof_verify_returnsFalse() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
bytes memory proof = issueProofWithEIP712Domain(
nonAdminKey,
bytes(adminFamName),
bytes(adminFamVersion),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(admin);
assertEq(
adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce),
abi.encodePacked(fundsReceiver),
proof
),
false
);
}
/**
* @notice assert that verify returns false for proofs where the id in the proof is different
* than the id in the call to verify.
*/
function test_proofWithWrongId_verify_returnsFalse() external {
bytes32 nonce = faucetHelper.consumeNonce();
address fundsReceiver = makeAddr("fundsReceiver");
address randomAddress = makeAddr("randomAddress");
bytes memory proof = issueProofWithEIP712Domain(
adminKey,
bytes(adminFamName),
bytes(adminFamVersion),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(admin);
assertEq(
adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce),
abi.encodePacked(randomAddress),
proof
),
false
);
}
}
packages/contracts-periphery/contracts/foundry-tests/Faucet.t.sol
0 → 100644
View file @
a5a55290
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { Faucet } from "../universal/faucet/Faucet.sol";
import { AdminFaucetAuthModule } from "../universal/faucet/authmodules/AdminFaucetAuthModule.sol";
import { FaucetHelper } from "../testing/helpers/FaucetHelper.sol";
contract Faucet_Initializer is Test {
event Drip(
string indexed authModule,
bytes indexed userId,
uint256 amount,
address indexed recipient
);
address internal faucetContractAdmin;
address internal faucetAuthAdmin;
address internal nonAdmin;
address internal fundsReceiver;
uint256 internal faucetAuthAdminKey;
uint256 internal nonAdminKey;
uint256 internal startingTimestamp = 1000;
Faucet faucet;
AdminFaucetAuthModule optimistNftFam;
string internal optimistNftFamName = "OptimistNftFam";
string internal optimistNftFamVersion = "1";
AdminFaucetAuthModule githubFam;
string internal githubFamName = "GithubFam";
string internal githubFamVersion = "1";
FaucetHelper faucetHelper;
function setUp() public {
vm.warp(startingTimestamp);
faucetContractAdmin = makeAddr("faucetContractAdmin");
fundsReceiver = makeAddr("fundsReceiver");
faucetAuthAdminKey = 0xB0B0B0B0;
faucetAuthAdmin = vm.addr(faucetAuthAdminKey);
nonAdminKey = 0xC0C0C0C0;
nonAdmin = vm.addr(nonAdminKey);
_initializeContracts();
}
/**
* @notice Instantiates a Faucet.
*/
function _initializeContracts() internal {
faucet = new Faucet(faucetContractAdmin);
// Fill faucet with ether.
vm.deal(address(faucet), 10 ether);
vm.deal(address(faucetContractAdmin), 5 ether);
vm.deal(address(nonAdmin), 5 ether);
optimistNftFam = new AdminFaucetAuthModule(
faucetAuthAdmin,
optimistNftFamName,
optimistNftFamVersion
);
githubFam = new AdminFaucetAuthModule(faucetAuthAdmin, githubFamName, githubFamVersion);
faucetHelper = new FaucetHelper();
}
function _enableFaucetAuthModules() internal {
vm.startPrank(faucetContractAdmin);
faucet.configure(
optimistNftFam,
Faucet.ModuleConfig("OptimistNftModule", true, 1 days, 1 ether)
);
faucet.configure(githubFam, Faucet.ModuleConfig("GithubModule", true, 1 days, .05 ether));
vm.stopPrank();
}
/**
* @notice Get signature as a bytes blob.
*
*/
function _getSignature(uint256 _signingPrivateKey, bytes32 _digest)
internal
pure
returns (bytes memory)
{
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest);
bytes memory signature = abi.encodePacked(r, s, v);
return signature;
}
/**
* @notice Signs a proof with the given private key and returns the signature using
* the given EIP712 domain separator. This assumes that the issuer's address is the
* corresponding public key to _issuerPrivateKey.
*/
function issueProofWithEIP712Domain(
uint256 _issuerPrivateKey,
bytes memory _eip712Name,
bytes memory _contractVersion,
uint256 _eip712Chainid,
address _eip712VerifyingContract,
address recipient,
bytes memory id,
bytes32 nonce
) internal view returns (bytes memory) {
AdminFaucetAuthModule.Proof memory proof = AdminFaucetAuthModule.Proof(
recipient,
nonce,
id
);
return
_getSignature(
_issuerPrivateKey,
faucetHelper.getDigestWithEIP712Domain(
proof,
_eip712Name,
_contractVersion,
_eip712Chainid,
_eip712VerifyingContract
)
);
}
}
contract FaucetTest is Faucet_Initializer {
function test_initialize() external {
assertEq(faucet.ADMIN(), faucetContractAdmin);
}
function test_authAdmin_drip_succeeds() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(optimistNftFamName),
bytes(optimistNftFamVersion),
block.chainid,
address(optimistNftFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(optimistNftFam, abi.encodePacked(fundsReceiver), signature)
);
}
function test_nonAdmin_drip_fails() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
nonAdminKey,
bytes(optimistNftFamName),
bytes(optimistNftFamVersion),
block.chainid,
address(optimistNftFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(nonAdmin);
vm.expectRevert("Faucet: drip parameters could not be verified by security module");
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(optimistNftFam, abi.encodePacked(fundsReceiver), signature)
);
}
function test_drip_optimistNft_sendsCorrectAmount() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(optimistNftFamName),
bytes(optimistNftFamVersion),
block.chainid,
address(optimistNftFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
uint256 recipientBalanceBefore = address(fundsReceiver).balance;
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(optimistNftFam, abi.encodePacked(fundsReceiver), signature)
);
uint256 recipientBalanceAfter = address(fundsReceiver).balance;
assertEq(
recipientBalanceAfter - recipientBalanceBefore,
1 ether,
"expect increase of 1 ether"
);
}
function test_drip_github_sendsCorrectAmount() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
uint256 recipientBalanceBefore = address(fundsReceiver).balance;
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
uint256 recipientBalanceAfter = address(fundsReceiver).balance;
assertEq(
recipientBalanceAfter - recipientBalanceBefore,
.05 ether,
"expect increase of .05 ether"
);
}
function test_drip_emitsEvent() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.expectEmit(true, true, true, true, address(faucet));
emit Drip("GithubModule", abi.encodePacked(fundsReceiver), .05 ether, fundsReceiver);
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
}
function test_drip_disabledModule_reverts() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.startPrank(faucetContractAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
faucet.configure(githubFam, Faucet.ModuleConfig("GithubModule", false, 1 days, .05 ether));
vm.expectRevert("Faucet: provided auth module is not supported by this faucet");
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
vm.stopPrank();
}
function test_drip_preventsReplayAttacks() external {
_enableFaucetAuthModules();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.startPrank(faucetContractAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
vm.expectRevert("Faucet: nonce has already been used");
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature)
);
vm.stopPrank();
}
function test_drip_beforeTimeout_reverts() external {
_enableFaucetAuthModules();
bytes32 nonce0 = faucetHelper.consumeNonce();
bytes memory signature0 = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce0
);
vm.startPrank(faucetContractAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce0),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature0)
);
bytes32 nonce1 = faucetHelper.consumeNonce();
bytes memory signature1 = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce1
);
vm.expectRevert("Faucet: auth cannot be used yet because timeout has not elapsed");
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce1),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature1)
);
vm.stopPrank();
}
function test_drip_afterTimeout_succeeds() external {
_enableFaucetAuthModules();
bytes32 nonce0 = faucetHelper.consumeNonce();
bytes memory signature0 = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce0
);
vm.startPrank(faucetContractAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce0),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature0)
);
bytes32 nonce1 = faucetHelper.consumeNonce();
bytes memory signature1 = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes(githubFamName),
bytes(githubFamVersion),
block.chainid,
address(githubFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce1
);
vm.warp(startingTimestamp + 1 days + 1 seconds);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce1),
Faucet.AuthParameters(githubFam, abi.encodePacked(fundsReceiver), signature1)
);
vm.stopPrank();
}
function test_withdraw_succeeds() external {
vm.startPrank(faucetContractAdmin);
uint256 recipientBalanceBefore = address(fundsReceiver).balance;
faucet.withdraw(payable(fundsReceiver), 2 ether);
uint256 recipientBalanceAfter = address(fundsReceiver).balance;
assertEq(
recipientBalanceAfter - recipientBalanceBefore,
2 ether,
"expect increase of 2 ether"
);
vm.stopPrank();
}
function test_withdraw_nonAdmin_fails() external {
vm.prank(nonAdmin);
vm.expectRevert("Faucet: function can only be called by admin");
faucet.withdraw(payable(fundsReceiver), 2 ether);
}
function test_receive_succeeds() external {
uint256 faucetBalanceBefore = address(faucet).balance;
vm.prank(nonAdmin);
(bool success, ) = address(faucet).call{ value: 1 ether }("");
assertTrue(success);
uint256 faucetBalanceAfter = address(faucet).balance;
assertEq(faucetBalanceAfter - faucetBalanceBefore, 1 ether, "expect increase of 1 ether");
}
}
packages/contracts-periphery/contracts/testing/helpers/FaucetHelper.sol
0 → 100644
View file @
a5a55290
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {
AdminFaucetAuthModule
} from "../../universal/faucet/authmodules/AdminFaucetAuthModule.sol";
import {
ECDSAUpgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol";
/**
* Simple helper contract that helps with testing the Faucet contract.
*/
contract FaucetHelper {
/**
* @notice EIP712 typehash for the Proof type.
*/
bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes id)");
/**
* @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature.
*/
bytes32 public constant EIP712_DOMAIN_TYPEHASH =
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
);
/**
* @notice Keeps track of current nonce to generate new nonces for each drip.
*/
uint256 public currentNonce;
/**
* @notice Returns a bytes32 nonce that should change everytime. In practice, people should use
* pseudorandom nonces.
*
* @return Nonce that should be used as part of drip parameters.
*/
function consumeNonce() public returns (bytes32) {
return bytes32(keccak256(abi.encode(currentNonce++)));
}
/**
* @notice Returns the hash of the struct Proof.
*
* @param _proof Proof struct to hash.
*
* @return EIP-712 typed struct hash.
*/
function getProofStructHash(AdminFaucetAuthModule.Proof memory _proof)
public
pure
returns (bytes32)
{
return keccak256(abi.encode(PROOF_TYPEHASH, _proof.recipient, _proof.nonce, _proof.id));
}
/**
* @notice Computes the EIP712 digest with the given domain parameters.
* Used for testing that different domain parameters fail.
*
* @param _proof Proof struct to hash.
* @param _name Contract name to use in the EIP712 domain.
* @param _version Contract version to use in the EIP712 domain.
* @param _chainid Chain ID to use in the EIP712 domain.
* @param _verifyingContract Address to use in the EIP712 domain.
* @param _verifyingContract Address to use in the EIP712 domain.
* @param _verifyingContract Address to use in the EIP712 domain.
*
* @return EIP-712 compatible digest.
*/
function getDigestWithEIP712Domain(
AdminFaucetAuthModule.Proof memory _proof,
bytes memory _name,
bytes memory _version,
uint256 _chainid,
address _verifyingContract
) public pure returns (bytes32) {
bytes32 domainSeparator = keccak256(
abi.encode(
EIP712_DOMAIN_TYPEHASH,
keccak256(_name),
keccak256(_version),
_chainid,
_verifyingContract
)
);
return ECDSAUpgradeable.toTypedDataHash(domainSeparator, getProofStructHash(_proof));
}
}
packages/contracts-periphery/contracts/universal/faucet/Faucet.sol
0 → 100644
View file @
a5a55290
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
/**
* @title SafeSend
* @notice Sends ETH to a recipient account without triggering any code.
*/
contract SafeSend {
/**
* @param _recipient Account to send ETH to.
*/
constructor(address payable _recipient) payable {
selfdestruct(_recipient);
}
}
/**
* @title Faucet
* @notice Faucet contract that drips ETH to users.
*/
contract Faucet {
/**
* @notice Emitted on each drip.
*
* @param authModule The type of authentication that was used for verifying the drip.
* @param userId The id of the user that requested the drip.
* @param amount The amount of funds sent.
* @param recipient The recipient of the drip.
*/
event Drip(
string indexed authModule,
bytes indexed userId,
uint256 amount,
address indexed recipient
);
/**
* @notice Parameters for a drip.
*/
struct DripParameters {
address payable recipient;
bytes32 nonce;
}
/**
* @notice Parameters for authentication.
*/
struct AuthParameters {
IFaucetAuthModule module;
bytes id;
bytes proof;
}
/**
* @notice Configuration for an authentication module.
*/
struct ModuleConfig {
string name;
bool enabled;
uint256 ttl;
uint256 amount;
}
/**
* @notice Admin address that can configure the faucet.
*/
address public immutable ADMIN;
/**
* @notice Mapping of authentication modules to their configurations.
*/
mapping(IFaucetAuthModule => ModuleConfig) public modules;
/**
* @notice Mapping of authentication IDs to the next timestamp at which they can be used.
*/
mapping(IFaucetAuthModule => mapping(bytes => uint256)) public timeouts;
/**
* @notice Maps from id to nonces to whether or not they have been used.
*/
mapping(bytes => mapping(bytes32 => bool)) public nonces;
/**
* @notice Modifier that makes a function admin priviledged.
*/
modifier priviledged() {
require(msg.sender == ADMIN, "Faucet: function can only be called by admin");
_;
}
/**
* @param _admin Admin address that can configure the faucet.
*/
constructor(address _admin) {
ADMIN = _admin;
}
/**
* @notice Allows users to donate ETH to this contract.
*/
receive() external payable {
// Thank you!
}
/**
* @notice Allows the admin to withdraw funds.
*
* @param _recipient Address to receive the funds.
* @param _amount Amount of ETH in wei to withdraw.
*/
function withdraw(address payable _recipient, uint256 _amount) public priviledged {
new SafeSend{ value: _amount }(_recipient);
}
/**
* @notice Allows the admin to configure an authentication module.
*
* @param _module Authentication module to configure.
* @param _config Configuration to set for the module.
*/
function configure(IFaucetAuthModule _module, ModuleConfig memory _config) public priviledged {
modules[_module] = _config;
}
/**
* @notice Drips ETH to a recipient account.
*
* @param _params Drip parameters.
* @param _auth Authentication parameters.
*/
function drip(DripParameters memory _params, AuthParameters memory _auth) public {
// Grab the module config once.
ModuleConfig memory config = modules[_auth.module];
// Make sure we're using a supported security module.
require(config.enabled, "Faucet: provided auth module is not supported by this faucet");
// The issuer's signature commits to a nonce to prevent replay attacks.
// This checks that the nonce has not been used for this issuer before. The nonces are
// scoped to the issuer address, so the same nonce can be used by different issuers without
// clashing.
require(nonces[_auth.id][_params.nonce] == false, "Faucet: nonce has already been used");
// Make sure the timeout has elapsed.
require(
timeouts[_auth.module][_auth.id] < block.timestamp,
"Faucet: auth cannot be used yet because timeout has not elapsed"
);
// Verify the proof.
require(
_auth.module.verify(_params, _auth.id, _auth.proof),
"Faucet: drip parameters could not be verified by security module"
);
// 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);
emit Drip(config.name, _auth.id, config.amount, _params.recipient);
}
}
packages/contracts-periphery/contracts/universal/faucet/authmodules/AdminFaucetAuthModule.sol
0 → 100644
View file @
a5a55290
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import { IFaucetAuthModule } from "./IFaucetAuthModule.sol";
import { Faucet } from "../Faucet.sol";
/**
* @title AdminFaucetAuthModule
* @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin
* as the constructor argument.
*/
contract AdminFaucetAuthModule is IFaucetAuthModule, EIP712 {
/**
* @notice Admin address that can sign off on drips.
*/
address public immutable ADMIN;
/**
* @notice EIP712 typehash for the Proof type.
*/
bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes id)");
/**
* @notice Struct that represents a proof that verifies the admin.
*
* @custom:field recipient Address that will be receiving the faucet funds.
* @custom:field nonce Pseudorandom nonce to prevent replay attacks.
* @custom:field id id for the user requesting the faucet funds.
*/
struct Proof {
address recipient;
bytes32 nonce;
bytes id;
}
/**
* @param _admin Admin address that can sign off on drips.
* @param _name Contract name.
* @param _version The current major version of the signing domain.
*/
constructor(
address _admin,
string memory _name,
string memory _version
) EIP712(_name, _version) {
ADMIN = _admin;
}
/**
* @inheritdoc IFaucetAuthModule
*/
function verify(
Faucet.DripParameters memory _params,
bytes memory _id,
bytes memory _proof
) external view returns (bool) {
// Generate a EIP712 typed data hash to compare against the proof.
return
SignatureChecker.isValidSignatureNow(
ADMIN,
_hashTypedDataV4(
keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id))
),
_proof
);
}
}
packages/contracts-periphery/contracts/universal/faucet/authmodules/IFaucetAuthModule.sol
0 → 100644
View file @
a5a55290
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Faucet } from "../Faucet.sol";
/**
* @title IFaucetAuthModule
* @notice Interface for faucet authentication modules.
*/
interface IFaucetAuthModule {
/**
* @notice Verifies that the given drip parameters are valid.
*
* @param _params Drip parameters to verify.
* @param _id Authentication ID to verify.
* @param _proof Authentication proof to verify.
*/
function verify(
Faucet.DripParameters memory _params,
bytes memory _id,
bytes memory _proof
) external view returns (bool);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment