Skip to content

Commit

Permalink
feat(contracts): add eas registry contract
Browse files Browse the repository at this point in the history
- [x] Add BaseRegistry contract
- [x] Add EASRegistry contract
- [x] Add tests
  • Loading branch information
0xmad committed Aug 14, 2024
1 parent 926364d commit b931f1a
Show file tree
Hide file tree
Showing 14 changed files with 522 additions and 105 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,9 @@ jobs:
run: |
pnpm install
- name: Build
run: |
pnpm build
- name: ${{ matrix.command }}
run: pnpm run ${{ matrix.command }}
1 change: 1 addition & 0 deletions .github/workflows/contracts-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
- name: Build
run: |
pnpm run build
working-directory: packages/contracts

- name: Test
run: pnpm run test
Expand Down
3 changes: 3 additions & 0 deletions packages/contracts/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ GAS_PRICE=
FORKING_BLOCK_NUM=
# Hardhat logging level (true/false)
HARDHAT_LOGGING=
# Block gas limit
BLOCK_GAS_LIMIT=

1 change: 1 addition & 0 deletions packages/contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ deployed-contracts.json
cache
artifacts
typechain-types
coverage.json

35 changes: 35 additions & 0 deletions packages/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.20;

/// @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);
}
19 changes: 13 additions & 6 deletions packages/contracts/contracts/interfaces/IRecipientRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,23 @@ pragma solidity ^0.8.20;
interface IRecipientRegistry {
/// @notice A struct representing a recipient
struct Recipient {
// recipient id (optional)
bytes32 id;
// recipient metadata url
bytes32 metadataUrl;
// recipient address
address recipient;
}

/// @notice Events
event RecipientAdded(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed recipient);
event RecipientRemoved(uint256 indexed index, bytes32 id, address indexed recipient);
event RecipientChanged(uint256 indexed index, bytes32 id, bytes32 indexed metadataUrl, address indexed newAddress);

/// @notice Custom errors
error MaxRecipientsReached();
error InvalidIndex();

/// @notice Get a registry metadata url
/// @return The metadata url in bytes32 format
function getRegistryMetadataUrl() external view returns (bytes32);
Expand All @@ -27,7 +38,7 @@ interface IRecipientRegistry {

/// @notice Change a recipient
/// @param index The index of the recipient
function changeRecipient(uint256 index, Recipient calldata recipient) external view;
function changeRecipient(uint256 index, Recipient calldata recipient) external;

/// @notice Get a recipient
/// @param index The index of the recipient
Expand All @@ -38,11 +49,7 @@ interface IRecipientRegistry {
/// @return The max number of recipients
function maxRecipients() external view returns (uint256);

/// @notice Set the max number of recipients
/// @return The max number of recipients
function setMaxRecipients(uint256 maxRecipients) external returns (uint256);

/// @notice Get the number of recipients
/// @return The number of recipients
function getRecipientCount() external view returns (uint256);
function recipientCount() external view returns (uint256);
}
56 changes: 56 additions & 0 deletions packages/contracts/contracts/mocks/MockEAS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

/// @title MockEAS
/// @notice A mock contract to test the EASGatekeeper
contract MockEAS is IEAS {
address public immutable attester;
bytes32 public immutable schema;
address public immutable recipient;

/// @param _attester The address of the attester
/// @param _schema The schema of the attestation
/// @param _recipient The recipient of the attestation
constructor(address _attester, bytes32 _schema, address _recipient) {
attester = _attester;
schema = _schema;
recipient = _recipient;
}

/// @inheritdoc IEAS
function getAttestation(bytes32 attestationId) external view override returns (Attestation memory) {
// revoked
if (attestationId == 0x0000000000000000000000000000000000000000000000000000000000000001) {
return
Attestation({
uid: "0x000000000000000000000000000001",
schema: schema,
time: 0,
expirationTime: 0,
revocationTime: 1,
refUID: "0x000000000000000000000000000001",
recipient: recipient,
attester: attester,
revocable: true,
data: ""
});
// valid
} else {
return
Attestation({
uid: "0x000000000000000000000000000001",
schema: schema,
time: 0,
expirationTime: 0,
revocationTime: 0,
refUID: "0x000000000000000000000000000001",
recipient: recipient,
attester: attester,
revocable: false,
data: ""
});
}
}
}
80 changes: 80 additions & 0 deletions packages/contracts/contracts/registry/BaseRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { IRecipientRegistry } from "../interfaces/IRecipientRegistry.sol";

/// @title BaseRegistry
/// @notice Base contract for a registry
abstract contract BaseRegistry is IRecipientRegistry {
/// @notice The storage of recipients
mapping(uint256 => Recipient) internal recipients;

/// @inheritdoc IRecipientRegistry
uint256 public immutable maxRecipients;

/// @inheritdoc IRecipientRegistry
uint256 public recipientCount;

/// @notice The registry metadata url
bytes32 public immutable metadataUrl;

/// @notice Create a new instance of the registry contract
/// @param _maxRecipients The maximum number of recipients that can be registered
/// @param _metadataUrl The metadata url
constructor(uint256 _maxRecipients, bytes32 _metadataUrl) payable {
maxRecipients = _maxRecipients;
metadataUrl = _metadataUrl;
}

/// @inheritdoc IRecipientRegistry
function getRegistryMetadataUrl() public view virtual override returns (bytes32) {
return metadataUrl;
}

/// @inheritdoc IRecipientRegistry
function addRecipient(Recipient calldata recipient) public virtual override returns (uint256) {
uint256 index = recipientCount;

if (index >= maxRecipients) {
revert MaxRecipientsReached();
}

recipients[index] = recipient;
recipientCount++;

emit RecipientAdded(index, recipient.id, recipient.metadataUrl, recipient.recipient);

return index;
}

/// @inheritdoc IRecipientRegistry
function removeRecipient(uint256 index) public virtual override {
if (index >= recipientCount) {
revert InvalidIndex();
}

Recipient memory recipient = recipients[index];

delete recipients[index];

emit RecipientRemoved(index, recipient.id, recipient.recipient);
}

/// @inheritdoc IRecipientRegistry
function changeRecipient(uint256 index, Recipient calldata recipient) public virtual override {
if (index >= recipientCount) {
revert InvalidIndex();
}

recipients[index].id = recipient.id;
recipients[index].recipient = recipient.recipient;
recipients[index].metadataUrl = metadataUrl;

emit RecipientChanged(index, recipient.id, recipient.metadataUrl, recipient.recipient);
}

/// @inheritdoc IRecipientRegistry
function getRecipient(uint256 index) public view virtual override returns (Recipient memory) {
return recipients[index];
}
}
60 changes: 60 additions & 0 deletions packages/contracts/contracts/registry/EASRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

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

contract EASRegistry is Ownable, BaseRegistry, IEAS {
/// @notice The EAS contract
IEAS public immutable eas;

/// @notice Create a new instance of the registry contract
/// @param _maxRecipients The maximum number of projects that can be registered
/// @param _metadataUrl The metadata url
/// @param _eas The EAS address
constructor(
uint256 _maxRecipients,
bytes32 _metadataUrl,
address _eas
) payable Ownable(msg.sender) BaseRegistry(_maxRecipients, _metadataUrl) {
eas = IEAS(_eas);
}

/// @notice Add multiple recipients to the registry
/// @param recipients The recipients
function addRecipients(Recipient[] calldata recipients) external onlyOwner {
uint256 length = recipients.length;

for (uint256 i = 0; i < length; ) {
addRecipient(recipients[i]);

unchecked {
i++;
}
}
}

/// @inheritdoc BaseRegistry
function addRecipient(Recipient calldata recipient) public override onlyOwner returns (uint256) {
return super.addRecipient(recipient);
}

/// @notice Edit the address of a project
/// @param index The index of the project to edit
/// @param recipient The new recipient
function changeRecipient(uint256 index, Recipient calldata recipient) public override onlyOwner {
super.changeRecipient(index, recipient);
}

/// @inheritdoc BaseRegistry
function removeRecipient(uint256 index) public override onlyOwner {
super.removeRecipient(index);
}

/// @inheritdoc IEAS
function getAttestation(bytes32 id) public view override returns (Attestation memory) {
return eas.getAttestation(id);
}
}
2 changes: 1 addition & 1 deletion packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EChainId, ESupportedChains, getEtherscanApiKeys, getNetworkRpcUrls } fr

dotenv.config();

const DEFAULT_BLOCK_GAS_LIMIT = 30_000_000;
const DEFAULT_BLOCK_GAS_LIMIT = process.env.BLOCK_GAS_LIMIT ? Number(process.env.BLOCK_GAS_LIMIT) : 30_000_000;
const DEFAULT_GAS_MUL = 2;
const TEST_MNEMONIC = "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat";
const NETWORKS_RPC_URL = getNetworkRpcUrls();
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@types/chai": "^4.3.11",
"@types/lowdb": "^1.0.15",
"@types/mocha": "^10.0.7",
"@types/node": "^22.1.0",
"@types/node": "^22.2.0",
"@types/snarkjs": "^0.7.8",
"@types/uuid": "^10.0.0",
"chai": "^4.3.10",
Expand Down
2 changes: 0 additions & 2 deletions packages/contracts/tasks/helpers/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ export enum EChainId {
Coverage = 1337,
}

export const STATE_TREE_ARITY = 5;

/**
* Get network rpc urls object
*
Expand Down
Loading

0 comments on commit b931f1a

Please sign in to comment.