Skip to content

Commit

Permalink
Merge pull request #1109 from privacy-scaling-explorations/feat/eas-g…
Browse files Browse the repository at this point in the history
…atekeeper

feat(eas-gatekeeper): implement an EAS gatekeeper for MACI
  • Loading branch information
ctrlc03 authored Jan 29, 2024
2 parents d2f70b2 + 8b25d94 commit 22a0e27
Show file tree
Hide file tree
Showing 9 changed files with 324 additions and 11 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/contracts-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ jobs:
- name: Test
run: pnpm run test
working-directory: contracts
env:
OP_RPC_URL: ${{ secrets.OP_RPC_URL }}
2 changes: 1 addition & 1 deletion contracts/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MNEMONIC=
ETHERSCAN_API_KEY=
INFURA_KEY=

OP_RPC_URL=
87 changes: 87 additions & 0 deletions contracts/contracts/gatekeepers/EASGatekeeper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";

import { SignUpGatekeeper } from "./SignUpGatekeeper.sol";
import { IEAS } from "../interfaces/IEAS.sol";

/// @title EASGatekeeper
/// @notice A gatekeeper contract which allows users to sign up to MACI
/// only if they own an attestation from a valid schema and trusted attester.
contract EASGatekeeper is SignUpGatekeeper, Ownable {
// the reference to the EAS contract
IEAS private immutable eas;

// the schema to check against
bytes32 public immutable schema;

// the trusted attester
address public immutable attester;

/// @notice the reference to the MACI contract
address public maci;

// a mapping of attestations that have already registered
mapping(bytes32 => bool) public registeredAttestations;

/// @notice custom errors
error AttestationRevoked();
error AlreadyRegistered();
error AttesterNotTrusted();
error NotYourAttestation();
error InvalidSchema();
error OnlyMACI();
error ZeroAddress();

/// @notice Deploy an instance of EASGatekeeper
/// @param _eas The EAS contract
/// @param _attester The trusted attester
/// @param _schema The schema UID
constructor(address _eas, address _attester, bytes32 _schema) payable Ownable() {
if (_eas == address(0) || _attester == address(0)) revert ZeroAddress();
eas = IEAS(_eas);
schema = _schema;
attester = _attester;
}

/// @notice Adds an uninitialised MACI instance to allow for token signups
/// @param _maci The MACI contract interface to be stored
function setMaciInstance(address _maci) public override onlyOwner {
if (_maci == address(0)) revert ZeroAddress();
maci = _maci;
}

/// @notice Register an user based on their attestation
/// @dev Throw if the attestation is not valid or just complete silently
/// @param _user The user's Ethereum address.
/// @param _data The ABI-encoded schemaId as a uint256.
function register(address _user, bytes memory _data) public override {
// decode the argument
bytes32 attestationId = abi.decode(_data, (bytes32));

// ensure that the caller is the MACI contract
if (maci != msg.sender) revert OnlyMACI();

// ensure that the attestation has not been registered yet
if (registeredAttestations[attestationId]) revert AlreadyRegistered();

// register the attestation so it cannot be called again with the same one
registeredAttestations[attestationId] = true;

// get the attestation from the EAS contract
IEAS.Attestation memory attestation = eas.getAttestation(attestationId);

// the schema must match
if (attestation.schema != schema) revert InvalidSchema();

// we check that the attestation attester is the trusted one
if (attestation.attester != attester) revert AttesterNotTrusted();

// we check that it was not revoked
if (attestation.revocationTime != 0) revert AttestationRevoked();

// one cannot register an attestation for another user
if (attestation.recipient != _user) revert NotYourAttestation();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import { MACI } from "../MACI.sol";
import { SignUpGatekeeper } from "./SignUpGatekeeper.sol";

/// @title FreeForAllGatekeeper
Expand All @@ -14,7 +13,7 @@ contract FreeForAllGatekeeper is SignUpGatekeeper {
/// @notice setMaciInstance does nothing in this gatekeeper
/// @param _maci The MACI contract
// solhint-disable-next-line no-empty-blocks
function setMaciInstance(MACI _maci) public override {}
function setMaciInstance(address _maci) public override {}

/// @notice Registers the user without any restrictions.
/// @param _address The address of the user
Expand Down
4 changes: 1 addition & 3 deletions contracts/contracts/gatekeepers/SignUpGatekeeper.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

import { MACI } from "../MACI.sol";

/// @title SignUpGatekeeper
/// @notice A gatekeeper contract which allows users to sign up for a poll.
abstract contract SignUpGatekeeper {
/// @notice Allows to set the MACI contract
// solhint-disable-next-line no-empty-blocks
function setMaciInstance(MACI _maci) public virtual {}
function setMaciInstance(address _maci) public virtual {}

/// @notice Registers the user
/// @param _user The address of the user
Expand Down
8 changes: 3 additions & 5 deletions contracts/contracts/gatekeepers/SignUpTokenGatekeeper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { SignUpGatekeeper } from "./SignUpGatekeeper.sol";
import { SignUpToken } from "../SignUpToken.sol";

import { MACI } from "../MACI.sol";

/// @title SignUpTokenGatekeeper
/// @notice This contract allows to gatekeep MACI signups
/// by requiring new voters to own a certain ERC721 token
contract SignUpTokenGatekeeper is SignUpGatekeeper, Ownable {
/// @notice the reference to the SignUpToken contract
SignUpToken public token;
/// @notice the reference to the MACI contract
MACI public maci;
address public maci;

/// @notice a mapping of tokenIds to whether they have been used to sign up
mapping(uint256 => bool) public registeredTokenIds;
Expand All @@ -33,7 +31,7 @@ contract SignUpTokenGatekeeper is SignUpGatekeeper, Ownable {

/// @notice Adds an uninitialised MACI instance to allow for token signups
/// @param _maci The MACI contract interface to be stored
function setMaciInstance(MACI _maci) public override onlyOwner {
function setMaciInstance(address _maci) public override onlyOwner {
maci = _maci;
}

Expand All @@ -43,7 +41,7 @@ contract SignUpTokenGatekeeper is SignUpGatekeeper, Ownable {
/// @param _user The user's Ethereum address.
/// @param _data The ABI-encoded tokenId as a uint256.
function register(address _user, bytes memory _data) public override {
if (address(maci) != msg.sender) revert OnlyMACI();
if (maci != msg.sender) revert OnlyMACI();
// Decode the given _data bytes into a uint256 which is the token ID
uint256 tokenId = abi.decode(_data, (uint256));

Expand Down
35 changes: 35 additions & 0 deletions contracts/contracts/interfaces/IEAS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;

/// @title IEAS
/// @notice An interface to the EAS contract.
interface IEAS {
/// @notice A struct representing a single attestation.
struct Attestation {
// A unique identifier of the attestation.
bytes32 uid;
// The unique identifier of the schema.
bytes32 schema;
// The time when the attestation was created (Unix timestamp).
uint64 time;
// The time when the attestation expires (Unix timestamp).
uint64 expirationTime;
// The time when the attestation was revoked (Unix timestamp).
uint64 revocationTime;
// The UID of the related attestation.
bytes32 refUID;
// The recipient of the attestation.
address recipient;
// The attester/sender of the attestation.
address attester;
// Whether the attestation is revocable.
bool revocable;
// Custom attestation data.
bytes data;
}

/// Get an attestation by its unique identifier.
/// @param uid The unique identifier of the attestation.
/// @return attestation The attestation.
function getAttestation(bytes32 uid) external view returns (Attestation memory);
}
1 change: 1 addition & 0 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"test:vkRegistry": "hardhat test ./tests/VkRegistry.test.ts",
"test:pollFactory": "hardhat test ./tests/PollFactory.test.ts",
"test:subsidy": "hardhat test ./tests/Subsidy.test.ts",
"test:eas_gatekeeper": "hardhat test ./tests/EASGatekeeper.test.ts",
"deploy": "hardhat deploy-full",
"deploy:hardhat": "pnpm run deploy",
"deploy:sepolia": "pnpm run deploy --network sepolia"
Expand Down
Loading

0 comments on commit 22a0e27

Please sign in to comment.