Skip to content

Commit

Permalink
feat(contracts): add registry manager
Browse files Browse the repository at this point in the history
- [x] Add RegistryManager contract
- [x] Add EASRegistryManager contract
- [x] Add Common contract with common errors
- [x] Minor refactoring
- [x] Add tests
  • Loading branch information
0xmad committed Aug 23, 2024
1 parent e72cfbb commit 969aabd
Show file tree
Hide file tree
Showing 15 changed files with 946 additions and 43 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/slither.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Slither Analysis

on:
push:
branches: [main]
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
slither:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9

- name: Use Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"

- name: Install
run: |
pnpm install --frozen-lockfile --prefer-offline
- name: Build
run: |
pnpm run build
working-directory: packages/contracts

- name: Run Slither
uses: crytic/[email protected]
continue-on-error: true
id: slither
with:
sarif: results.sarif
fail-on: none
ignore-compile: true
node-version: 20
target: "packages/contracts/"

- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.slither.outputs.sarif }}
12 changes: 11 additions & 1 deletion packages/contracts/.solcover.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { buildPoseidonT3, buildPoseidonT4, buildPoseidonT5, buildPoseidonT6 } = require("maci-contracts");
const { poseidonContract } = require("circomlibjs");
const hre = require("hardhat");
const fs = require("fs");
const path = require("path");

Expand All @@ -8,6 +9,15 @@ const PATHS = [
path.resolve(__dirname, "..", "typechain-types"),
];

const buildPoseidon = async (numInputs) => {
await hre.overwriteArtifact(`PoseidonT${numInputs + 1}`, poseidonContract.createCode(numInputs));
};

const buildPoseidonT3 = () => buildPoseidon(2);
const buildPoseidonT4 = () => buildPoseidon(3);
const buildPoseidonT5 = () => buildPoseidon(4);
const buildPoseidonT6 = () => buildPoseidon(5);

module.exports = {
onPreCompile: async () => {
await Promise.all(
Expand Down
12 changes: 12 additions & 0 deletions packages/contracts/contracts/interfaces/ICommon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title ICommon
/// @notice Interface that contains common things for all the contracts
interface ICommon {
/// @notice custom errors
error InvalidAddress();
error InvalidInput();
error InvalidIndex();
error ValidationError();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ interface IRecipientRegistry {

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

/// @notice Get a registry metadata url
/// @return The metadata url in bytes32 format
Expand Down
86 changes: 86 additions & 0 deletions packages/contracts/contracts/interfaces/IRegistryManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

/// @title IRegistryManager
/// @notice An interface for a registry manager. Allows to manage requests for Registry.
interface IRegistryManager {
/// @notice Enum representing request type
enum RequestType {
Add,
Change,
Remove
}

/// @notice Enum representing request status
enum Status {
Pending,
Approved,
Rejected
}

/// @notice Request data
struct Request {
/// @notice index (optional)
uint256 index;
/// @notice registry address
address registry;
/// @notice request type
RequestType requestType;
/// @notice request status
Status status;
/// @notice recipient data
IRecipientRegistry.Recipient recipient;
}

/// @notice Events
event RequestSent(
address indexed registry,
RequestType indexed requestType,
address indexed recipient,
uint256 index,
bytes32 id,
bytes32 metadataUrl
);
event RequestApproved(
address indexed registry,
RequestType indexed requestType,
address indexed recipient,
uint256 index,
bytes32 id,
bytes32 metadataUrl
);
event RequestRejected(
address indexed registry,
RequestType indexed requestType,
address indexed recipient,
uint256 index,
bytes32 id,
bytes32 metadataUrl
);

/// @notice Custom errors
error OperationError();

/// @notice Send the request to the Registry
/// @param request user request
function process(Request calldata request) external;

/// @notice Approve the request and call registry function
/// @param index The index of the request
function approve(uint256 index) external;

/// @notice Reject the request
/// @param index The index of the request
function reject(uint256 index) external;

/// @notice Get a request
/// @param index The index of the request
/// @return request The request to the registry
function getRequest(uint256 index) external view returns (Request memory request);

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

import { BaseRegistry } from "../registry/BaseRegistry.sol";

/// @title MockRegistry
/// @notice Mock registry contract
contract MockRegistry is BaseRegistry {
/// @notice Create a new instance of the registry contract
/// @param max The maximum number of projects that can be registered
/// @param url The metadata url
/// @param ownerAddress The owner address
constructor(uint256 max, bytes32 url, address ownerAddress) payable BaseRegistry(max, url, ownerAddress) {}
}
22 changes: 13 additions & 9 deletions packages/contracts/contracts/registry/BaseRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

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

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

Expand All @@ -19,11 +22,12 @@ abstract contract BaseRegistry is IRecipientRegistry {
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;
/// @param max The maximum number of recipients that can be registered
/// @param url The metadata url
/// @param ownerAddress The owner address
constructor(uint256 max, bytes32 url, address ownerAddress) payable Ownable(ownerAddress) {
maxRecipients = max;
metadataUrl = url;
}

/// @inheritdoc IRecipientRegistry
Expand All @@ -32,7 +36,7 @@ abstract contract BaseRegistry is IRecipientRegistry {
}

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

if (index >= maxRecipients) {
Expand All @@ -52,7 +56,7 @@ abstract contract BaseRegistry is IRecipientRegistry {
}

Check warning

Code scanning / Slither

Costly operations inside a loop Warning


/// @inheritdoc IRecipientRegistry
function removeRecipient(uint256 index) public virtual override {
function removeRecipient(uint256 index) public virtual override onlyOwner {
if (index >= recipientCount) {
revert InvalidIndex();
}
Expand All @@ -66,7 +70,7 @@ abstract contract BaseRegistry is IRecipientRegistry {
}

/// @inheritdoc IRecipientRegistry
function changeRecipient(uint256 index, Recipient calldata recipient) public virtual override {
function changeRecipient(uint256 index, Recipient calldata recipient) public virtual override onlyOwner {
if (index >= recipientCount) {
revert InvalidIndex();
}
Expand Down
45 changes: 17 additions & 28 deletions packages/contracts/contracts/registry/EASRegistry.sol
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
// 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 {
/// @title EASRegistry
/// @notice EAS registry contract
contract EASRegistry is 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
/// @param max The maximum number of projects that can be registered
/// @param url The metadata url
/// @param easAddress The EAS address
/// @param ownerAddress The owner address
constructor(
uint256 _maxRecipients,
bytes32 _metadataUrl,
address _eas
) payable Ownable(msg.sender) BaseRegistry(_maxRecipients, _metadataUrl) {
eas = IEAS(_eas);
uint256 max,
bytes32 url,
address easAddress,
address ownerAddress
) payable BaseRegistry(max, url, ownerAddress) {
if (easAddress == address(0)) {
revert InvalidAddress();
}

eas = IEAS(easAddress);
}

/// @notice Add multiple recipients to the registry
Expand All @@ -36,23 +42,6 @@ contract EASRegistry is Ownable, BaseRegistry, IEAS {
}
}

/// @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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

/// @title EASRegistryManager
/// @notice Contract that allows to use send, approve, reject requests to EASRegistry.
contract EASRegistryManager is RegistryManager {
/// @notice custom errors
error NotYourAttestation();

/// @notice EAS
IEAS public immutable eas;

/// @notice Initialize EASRegistryManager
/// @param easAddress EAS contract address
constructor(address easAddress) payable {
if (easAddress == address(0)) {
revert InvalidAddress();
}

eas = IEAS(easAddress);
}

/// @notice Check recipient has an EAS attestation
/// @param request request to the registry
modifier onlyWithAttestation(Request memory request) {
if (request.requestType != RequestType.Change) {
_;
return;
}

IEAS.Attestation memory attestation = eas.getAttestation(request.recipient.id);

if (attestation.recipient != request.recipient.recipient) {
revert NotYourAttestation();
}

_;
}

/// @inheritdoc RegistryManager
function process(
Request calldata request
) public virtual override isValidRequest(request) onlyWithAttestation(request) {
super.process(request);
}
}
Loading

0 comments on commit 969aabd

Please sign in to comment.