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
49806f74
Commit
49806f74
authored
May 03, 2023
by
tre
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Create initial contract
parent
c3df5d35
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
536 additions
and
0 deletions
+536
-0
Faucet.t.sol
.../contracts-periphery/contracts/foundry-tests/Faucet.t.sol
+146
-0
FaucetHelper.sol
...acts-periphery/contracts/testing/helpers/FaucetHelper.sol
+97
-0
Faucet.sol
...contracts-periphery/contracts/universal/faucet/Faucet.sol
+293
-0
No files found.
packages/contracts-periphery/contracts/foundry-tests/Faucet.t.sol
0 → 100644
View file @
49806f74
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
/* Testing utilities */
import { Test } from "forge-std/Test.sol";
import { AdminFAM, Faucet } from "../universal/faucet/Faucet.sol";
import { FaucetHelper } from "../testing/helpers/FaucetHelper.sol";
contract Faucet_Initializer is Test {
address internal faucetContractAdmin;
address internal faucetAuthAdmin;
address internal nonAdmin;
address internal fundsReceiver;
uint256 internal faucetAuthAdminKey;
uint256 internal nonAdminKey;
Faucet faucet;
AdminFAM adminFam;
FaucetHelper faucetHelper;
function setUp() public {
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);
adminFam = new AdminFAM(faucetAuthAdmin);
adminFam.initialize("AdminFAM");
faucetHelper = new FaucetHelper();
}
function _enableFaucetAuthModule() internal {
vm.prank(faucetContractAdmin);
faucet.configure(adminFam, Faucet.ModuleConfig(true, 1 days, 1 ether));
}
/**
* @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,
uint256 nonce
) internal view returns (bytes memory) {
AdminFAM.Proof memory proof = AdminFAM.Proof(recipient, bytes32(keccak256(abi.encode(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 {
_enableFaucetAuthModule();
bytes memory signature
= issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
faucetHelper.currentNonce()
);
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), faucetHelper.consumeNonce()),
Faucet.AuthParameters(adminFam, abi.encodePacked(fundsReceiver), signature));
}
function test_nonAdmin_drip_fails() external {
_enableFaucetAuthModule();
bytes memory signature
= issueProofWithEIP712Domain(
nonAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
faucetHelper.currentNonce()
);
vm.prank(nonAdmin);
vm.expectRevert("Faucet: drip parameters could not be verified by security module");
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), faucetHelper.consumeNonce()),
Faucet.AuthParameters(adminFam, abi.encodePacked(fundsReceiver), signature));
}
}
packages/contracts-periphery/contracts/testing/helpers/FaucetHelper.sol
0 → 100644
View file @
49806f74
//SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { AdminFAM } from "../../universal/faucet/Faucet.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
/**
* Simple helper contract that helps with testing the Faucet contract.
*/
contract FaucetHelper {
/**
* @notice EIP712 typehash for the ClaimableInvite 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 invite.
*/
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 ClaimableInvite.
*
* @param _proof ClaimableInvite struct to hash.
*
* @return EIP-712 typed struct hash.
*/
function getProofStructHash(AdminFAM.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 ClaimableInvite 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(
AdminFAM.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
ECDSA.toTypedDataHash(domainSeparator, getProofStructHash(_proof));
}
}
packages/contracts-periphery/contracts/universal/faucet/Faucet.sol
0 → 100644
View file @
49806f74
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {
EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.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 FaucetAuthModule
* @notice Interface for faucet authentication modules.
*/
interface FaucetAuthModule {
/**
* @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
);
}
/**
* @title AdminFAM
* @notice FaucetAuthModule that allows an admin to sign off on a given faucet drip. Takes an admin
* as the constructor argument.
*/
contract AdminFAM is FaucetAuthModule, Semver, EIP712Upgradeable {
/**
* @notice Admin address that can sign off on drips.
*/
address public immutable ADMIN;
/**
* @notice EIP712 typehash for the ClaimableInvite type.
*/
bytes32 public constant PROOF_TYPEHASH =
keccak256("Proof(address recipient,bytes32 nonce,bytes id)");
// bytes32 public constant PROOF_TYPEHASH =
// keccak256("Proof(address recipient,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.
*/
constructor(
address admin
) Semver(1, 0, 0) {
ADMIN = admin;
}
/**
* @notice Initializes this contract, setting the EIP712 context.
*
* @param _name Contract name.
*/
function initialize(string memory _name) public initializer {
__EIP712_init(_name, version());
}
/**
* @inheritdoc FaucetAuthModule
*/
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.
bytes32 digest = _hashTypedDataV4(
keccak256(
abi.encode(
PROOF_TYPEHASH,
params.recipient,
params.nonce,
id
)
)
);
return SignatureChecker.isValidSignatureNow(ADMIN, digest, proof);
}
}
/**
* @title Faucet
* @notice Faucet contract that drips ETH to users.
*/
contract Faucet {
/**
* @notice Parameters for a drip.
*/
struct DripParameters {
address payable recipient;
bytes32 nonce;
}
/**
* @notice Parameters for authentication.
*/
struct AuthParameters {
FaucetAuthModule module;
bytes id;
bytes proof;
}
/**
* @notice Configuration for an authentication module.
*/
struct ModuleConfig {
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 (FaucetAuthModule => ModuleConfig) public modules;
/**
* @notice Mapping of authentication IDs to the next timestamp at which they can be used.
*/
mapping (FaucetAuthModule => 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 usedNonces;
/**
* @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(
FaucetAuthModule 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(
usedNonces[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;
// Execute a safe transfer of ETH to the recipient account.
new SafeSend{value: config.amount}(params.recipient);
}
}
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