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
3d4019d6
Commit
3d4019d6
authored
May 05, 2023
by
tre
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Address comments and split AuthModule into separate file
parent
5943c418
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
366 additions
and
207 deletions
+366
-207
AdminFaucetAuthModule.t.sol
...phery/contracts/foundry-tests/AdminFaucetAuthModule.t.sol
+185
-0
Faucet.t.sol
.../contracts-periphery/contracts/foundry-tests/Faucet.t.sol
+37
-33
FaucetHelper.sol
...acts-periphery/contracts/testing/helpers/FaucetHelper.sol
+11
-17
Faucet.sol
...contracts-periphery/contracts/universal/faucet/Faucet.sol
+38
-157
AdminFaucetAuthModule.sol
...ts/universal/faucet/authmodules/AdminFaucetAuthModule.sol
+72
-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 @
3d4019d6
//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 adminFam;
/**
* @notice An instance of the `FaucetHelper` contract.
*/
FaucetHelper faucetHelper;
/**
* @notice Deploy the `AdminFaucetAuthModule` contract.
*/
function setUp() external {
adminKey = 0xB0B0B0B0;
admin = vm.addr(adminKey);
nonAdminKey = 0xC0C0C0C0;
nonAdmin = vm.addr(nonAdminKey);
adminFam = new AdminFaucetAuthModule(admin);
adminFam.initialize("AdminFAM");
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("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(nonAdmin);
adminFam.verify(
Faucet.DripParameters(payable(fundsReceiver), nonce),
abi.encodePacked(fundsReceiver),
proof
);
}
/**
* @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("AdminFAM"),
bytes(adminFam.version()),
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("AdminFAM"),
bytes(adminFam.version()),
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
View file @
3d4019d6
...
...
@@ -2,7 +2,8 @@
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { AdminFAM, Faucet } from "../universal/faucet/Faucet.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 {
...
...
@@ -14,7 +15,7 @@ contract Faucet_Initializer is Test {
uint256 internal nonAdminKey;
Faucet faucet;
AdminF
AM
adminFam;
AdminF
aucetAuthModule
adminFam;
FaucetHelper faucetHelper;
...
...
@@ -31,7 +32,7 @@ contract Faucet_Initializer is Test {
_initializeContracts();
}
/**
/**
* @notice Instantiates a Faucet.
*/
function _initializeContracts() internal {
...
...
@@ -40,19 +41,18 @@ contract Faucet_Initializer is Test {
// Fill faucet with ether.
vm.deal(address(faucet), 10 ether);
adminFam = new AdminF
AM
(faucetAuthAdmin);
adminFam = new AdminF
aucetAuthModule
(faucetAuthAdmin);
adminFam.initialize("AdminFAM");
faucetHelper =
new FaucetHelper();
faucetHelper = new FaucetHelper();
}
function _enableFaucetAuthModule() internal {
vm.prank(faucetContractAdmin);
faucet.configure(adminFam, Faucet.ModuleConfig(true, 1 days, 1 ether));
faucet.configure(adminFam, Faucet.ModuleConfig(
"OptimistModule",
true, 1 days, 1 ether));
}
/**
/**
* @notice Get signature as a bytes blob.
*
*/
...
...
@@ -82,7 +82,11 @@ contract Faucet_Initializer is Test {
bytes memory id,
bytes32 nonce
) internal view returns (bytes memory) {
AdminFAM.Proof memory proof = AdminFAM.Proof(recipient, nonce, id);
AdminFaucetAuthModule.Proof memory proof = AdminFaucetAuthModule.Proof(
recipient,
nonce,
id
);
return
_getSignature(
_issuerPrivateKey,
...
...
@@ -105,43 +109,43 @@ contract FaucetTest is Faucet_Initializer {
function test_AuthAdmin_drip_succeeds() external {
_enableFaucetAuthModule();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature
= issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
bytes memory signature = issueProofWithEIP712Domain(
faucetAuthAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
vm.prank(nonAdmin);
faucet.drip(
Faucet.DripParameters(payable(fundsReceiver), nonce),
Faucet.AuthParameters(adminFam, abi.encodePacked(fundsReceiver), signature));
Faucet.AuthParameters(adminFam, abi.encodePacked(fundsReceiver), signature)
);
}
function test_nonAdmin_drip_fails() external {
_enableFaucetAuthModule();
bytes32 nonce = faucetHelper.consumeNonce();
bytes memory signature
= issueProofWithEIP712Domain(
nonAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
fundsReceiver,
abi.encodePacked(fundsReceiver),
nonce
);
bytes memory signature = issueProofWithEIP712Domain(
nonAdminKey,
bytes("AdminFAM"),
bytes(adminFam.version()),
block.chainid,
address(adminFam),
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(adminFam, abi.encodePacked(fundsReceiver), signature));
Faucet.AuthParameters(adminFam, abi.encodePacked(fundsReceiver), signature)
);
}
}
packages/contracts-periphery/contracts/testing/helpers/FaucetHelper.sol
View file @
3d4019d6
//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";
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.
...
...
@@ -44,23 +48,15 @@ contract FaucetHelper {
*
* @return EIP-712 typed struct hash.
*/
function getProofStructHash(AdminF
AM
.Proof memory _proof)
function getProofStructHash(AdminF
aucetAuthModule
.Proof memory _proof)
public
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
PROOF_TYPEHASH,
_proof.recipient,
_proof.nonce,
_proof.id
)
);
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.
*
...
...
@@ -75,7 +71,7 @@ contract FaucetHelper {
* @return EIP-712 compatible digest.
*/
function getDigestWithEIP712Domain(
AdminF
AM
.Proof memory _proof,
AdminF
aucetAuthModule
.Proof memory _proof,
bytes memory _name,
bytes memory _version,
uint256 _chainid,
...
...
@@ -90,8 +86,6 @@ contract FaucetHelper {
_verifyingContract
)
);
return
ECDSA.toTypedDataHash(domainSeparator, getProofStructHash(_proof));
return ECDSAUpgradeable.toTypedDataHash(domainSeparator, getProofStructHash(_proof));
}
}
packages/contracts-periphery/contracts/universal/faucet/Faucet.sol
View file @
3d4019d6
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import {
EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
import { IFaucetAuthModule } from "./authmodules/IFaucetAuthModule.sol";
/**
* @title SafeSend
...
...
@@ -13,110 +9,10 @@ import {
*/
contract SafeSend {
/**
* @param recipient Account to send ETH to.
* @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 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.
*/
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);
constructor(address payable _recipient) payable {
selfdestruct(_recipient);
}
}
...
...
@@ -137,7 +33,7 @@ contract Faucet {
* @notice Parameters for authentication.
*/
struct AuthParameters {
FaucetAuthModule module;
I
FaucetAuthModule module;
bytes id;
bytes proof;
}
...
...
@@ -146,6 +42,7 @@ contract Faucet {
* @notice Configuration for an authentication module.
*/
struct ModuleConfig {
string name;
bool enabled;
uint256 ttl;
uint256 amount;
...
...
@@ -159,115 +56,99 @@ contract Faucet {
/**
* @notice Mapping of authentication modules to their configurations.
*/
mapping
(
FaucetAuthModule => ModuleConfig) public modules;
mapping
(I
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;
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 usedNonces;
/**
/**
* @notice Modifier that makes a function admin priviledged.
*/
modifier priviledged() {
require(
msg.sender == ADMIN,
"Faucet: function can only be called by admin"
);
_;
require(msg.sender == ADMIN, "Faucet: function can only be called by admin");
_;
}
/**
* @param admin Admin address that can configure the faucet.
* @param
_
admin Admin address that can configure the faucet.
*/
constructor(
address admin
) {
ADMIN = admin;
constructor(address _admin) {
ADMIN = _admin;
}
/**
* @notice Allows users to donate ETH to this contract.
*/
receive() external payable {
// Thank you!
}
// 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.
* @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);
}
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.
* @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;
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.
* @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];
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"
);
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,
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,
timeouts[_auth.module][_auth.id] == 0 ||
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),
_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;
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);
new SafeSend{
value: config.amount }(_
params.recipient);
}
}
packages/contracts-periphery/contracts/universal/faucet/authmodules/AdminFaucetAuthModule.sol
0 → 100644
View file @
3d4019d6
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Semver } from "@eth-optimism/contracts-bedrock/contracts/universal/Semver.sol";
import {
EIP712Upgradeable
} from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.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, Semver, EIP712Upgradeable {
/**
* @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.
*/
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 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.
bytes32 digest = _hashTypedDataV4(
keccak256(abi.encode(PROOF_TYPEHASH, _params.recipient, _params.nonce, _id))
);
return SignatureChecker.isValidSignatureNow(ADMIN, digest, _proof);
}
}
packages/contracts-periphery/contracts/universal/faucet/authmodules/IFaucetAuthModule.sol
0 → 100644
View file @
3d4019d6
// 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