Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(contracts): add registry manager #274

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
0xmad marked this conversation as resolved.
Show resolved Hide resolved
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,
ctrlc03 marked this conversation as resolved.
Show resolved Hide resolved
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 memory 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 @@
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,27 +36,27 @@
}

/// @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) {
revert MaxRecipientsReached();
}

if (recipient.recipient == address(0)) {
revert InvalidInput();
}

recipients[index] = recipient;
recipientCount++;

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

return index;
}

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 @@
}

/// @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,60 +1,49 @@
// 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
/// @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);
}
}

Check warning

Code scanning / Slither

Contracts that lock Ether Medium

Contract locking ether found:
Contract EASRegistry has payable functions:
- BaseRegistry.constructor(uint256,string,address)
- EASRegistry.constructor(uint256,string,address,address)
But does not have a function to withdraw the ether
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 memory request
) public virtual override isValidRequest(request) onlyWithAttestation(request) {
super.process(request);
}
}
Loading
Loading