Skip to content

Commit

Permalink
feat(contracts): init porting of excubia core abstract contract and F…
Browse files Browse the repository at this point in the history
…reeForAll extension
  • Loading branch information
0xjei committed Oct 1, 2024
1 parent fe1c502 commit fad47c5
Show file tree
Hide file tree
Showing 11 changed files with 437 additions and 180 deletions.
29 changes: 0 additions & 29 deletions packages/contracts/contracts/src/Lock.sol

This file was deleted.

62 changes: 62 additions & 0 deletions packages/contracts/contracts/src/core/Excubia.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

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

/// @title Excubia.
/// @notice Abstract base contract which can be extended to implement a specific excubia.
/// @dev Inherit from this contract and implement the `_pass` & `_check` methods to define
/// your custom gatekeeping logic.
abstract contract Excubia is IExcubia, Ownable(msg.sender) {
/// @notice The excubia-protected contract address.
/// @dev The gate can be any contract address that requires a prior check to enable logic.
/// For example, the gate is a Semaphore group that requires the passerby
/// to meet certain criteria before joining.
address public gate;

/// @dev Modifier to restrict function calls to only from the gate address.
modifier onlyGate() {
if (msg.sender != gate) revert GateOnly();
_;
}

/// @inheritdoc IExcubia
function trait() external pure virtual returns (string memory) {}

/// @inheritdoc IExcubia
function setGate(address _gate) public virtual onlyOwner {
if (_gate == address(0)) revert ZeroAddress();
if (gate != address(0)) revert GateAlreadySet();

gate = _gate;

emit GateSet(_gate);
}

/// @inheritdoc IExcubia
function pass(address passerby, bytes calldata data) external onlyGate {
_pass(passerby, data);
}

/// @inheritdoc IExcubia
function check(address passerby, bytes calldata data) external view {
_check(passerby, data);
}

/// @notice Internal function to enforce the custom gate passing logic.
/// @dev Calls the `_check` internal logic and emits the relative event if successful.
/// @param passerby The address of the entity attempting to pass the gate.
/// @param data Additional data required for the check (e.g., encoded token identifier).
function _pass(address passerby, bytes calldata data) internal virtual {
_check(passerby, data);

emit GatePassed(passerby, gate);
}

/// @notice Internal function to define the custom gate protection logic.
/// @dev Custom logic to determine if the passerby can pass the gate.
/// @param passerby The address of the entity attempting to pass the gate.
/// @param data Additional data that may be required for the check.
function _check(address passerby, bytes calldata data) internal view virtual {}
}
50 changes: 50 additions & 0 deletions packages/contracts/contracts/src/core/IExcubia.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

/// @title IExcubia.
/// @notice Excubia contract interface.
interface IExcubia {
/// @notice Event emitted when someone passes the gate check.
/// @param passerby The address of those who have successfully passed the check.
/// @param gate The address of the excubia-protected contract address.
event GatePassed(address indexed passerby, address indexed gate);

/// @notice Event emitted when the gate address is set.
/// @param gate The address of the contract set as the gate.
event GateSet(address indexed gate);

/// @notice Error thrown when an address equal to zero is given.
error ZeroAddress();

/// @notice Error thrown when the gate address is not set.
error GateNotSet();

/// @notice Error thrown when the callee is not the gate contract.
error GateOnly();

/// @notice Error thrown when the gate address has been already set.
error GateAlreadySet();

/// @notice Error thrown when the passerby has already passed the gate.
error AlreadyPassed();

/// @notice Gets the trait of the Excubia contract.
/// @return The specific trait of the Excubia contract (e.g., SemaphoreExcubia has trait `Semaphore`).
function trait() external pure returns (string memory);

/// @notice Sets the gate address.
/// @dev Only the owner can set the destination gate address.
/// @param _gate The address of the contract to be set as the gate.
function setGate(address _gate) external;

/// @notice Enforces the custom gate passing logic.
/// @dev Must call the `check` to handle the logic of checking passerby for specific gate.
/// @param passerby The address of the entity attempting to pass the gate.
/// @param data Additional data required for the check (e.g., encoded token identifier).
function pass(address passerby, bytes calldata data) external;

/// @dev Defines the custom gate protection logic.
/// @param passerby The address of the entity attempting to pass the gate.
/// @param data Additional data that may be required for the check.
function check(address passerby, bytes calldata data) external view;
}
42 changes: 42 additions & 0 deletions packages/contracts/contracts/src/extensions/FreeForAllExcubia.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import {Excubia} from "../core/Excubia.sol";

/// @title FreeForAll Excubia Contract.
/// @notice This contract extends the Excubia contract to allow free access through the gate.
/// This contract does not perform any checks and allows any passerby to pass the gate.
/// @dev The contract overrides the `_check` function to always return true.
contract FreeForAllExcubia is Excubia {
/// @notice Constructor for the FreeForAllExcubia contract.
constructor() {}

/// @notice Mapping to track already passed passersby.
mapping(address => bool) public passedPassersby;

/// @notice The trait of the Excubia contract.
function trait() external pure override returns (string memory) {
return "FreeForAll";
}

/// @notice Internal function to handle the gate passing logic.
/// @dev This function calls the parent `_pass` function and then tracks the passerby.
/// @param passerby The address of the entity passing the gate.
/// @param data Additional data required for the pass (not used in this implementation).
function _pass(address passerby, bytes calldata data) internal override {
// Avoiding passing the gate twice with the same address.
if (passedPassersby[passerby]) revert AlreadyPassed();

passedPassersby[passerby] = true;

super._pass(passerby, data);
}

/// @notice Internal function to handle the gate protection logic.
/// @dev This function always returns true, signaling that any passerby is able to pass the gate.
/// @param passerby The address of the entity attempting to pass the gate.
/// @param data Additional data required for the check (e.g., encoded attestation ID).
function _check(address passerby, bytes calldata data) internal view override {
super._check(passerby, data);
}
}
147 changes: 147 additions & 0 deletions packages/contracts/contracts/test/FreeForAllExcubia.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.25 <0.9.0;

import {Test} from "forge-std/src/Test.sol";
import {FreeForAllExcubia} from "../src/extensions/FreeForAllExcubia.sol";

contract FreeForAllExcubiaTest is Test {
FreeForAllExcubia internal freeForAllExcubia;

address public owner = vm.addr(0x1);
address public gate = vm.addr(0x2);
address public passerbyA = vm.addr(0x3);
address public passerbyB = vm.addr(0x4);

event GateSet(address indexed gate);
event GatePassed(address indexed passerby, address indexed gate);

error OwnableUnauthorizedAccount(address);
error ZeroAddress();
error GateNotSet();
error GateOnly();
error GateAlreadySet();
error AlreadyPassed();

function setUp() public virtual {
vm.prank(owner);
freeForAllExcubia = new FreeForAllExcubia();
}

/**
* setGate()
*/
function testGateOnlyOwner() external {
vm.prank(address(0));
vm.expectRevert(abi.encodeWithSelector(OwnableUnauthorizedAccount.selector, address(0)));
freeForAllExcubia.setGate(gate);
}

function testGateZeroAddress() external {
vm.prank(owner);
vm.expectRevert(ZeroAddress.selector);
freeForAllExcubia.setGate(address(0));
}

function testSetGate() external {
vm.expectEmit(true, true, true, true);
emit GateSet(gate);

vm.prank(owner);
freeForAllExcubia.setGate(gate);

assertEq(freeForAllExcubia.gate(), gate);
}

function testGateAlreadySet() external {
vm.prank(owner);
freeForAllExcubia.setGate(gate);

vm.prank(owner);
vm.expectRevert(GateAlreadySet.selector);
freeForAllExcubia.setGate(gate);
}

function testTrait() external view {
assertEq(freeForAllExcubia.trait(), "FreeForAll");
}

/**
* pass() & implicitly _check()
*/
function testPassNotGate() external {
vm.prank(passerbyA);
vm.expectRevert(GateOnly.selector);
freeForAllExcubia.pass(passerbyA, "");
}

function testPass() external {
vm.prank(owner);
freeForAllExcubia.setGate(gate);

vm.expectEmit(true, true, true, true);
emit GatePassed(passerbyA, gate);

vm.prank(gate);
freeForAllExcubia.pass(passerbyA, "");

assertTrue(freeForAllExcubia.passedPassersby(passerbyA));
}

function testNotPassTwice() external {
vm.prank(owner);
freeForAllExcubia.setGate(gate);

assertEq(gate, freeForAllExcubia.gate());
vm.prank(gate);
freeForAllExcubia.pass(passerbyA, "");

assertTrue(freeForAllExcubia.passedPassersby(passerbyA));

vm.prank(gate);
vm.expectRevert(AlreadyPassed.selector);
freeForAllExcubia.pass(passerbyA, "");
}

function testPassAnotherPasserby() external {
vm.prank(owner);
freeForAllExcubia.setGate(gate);

vm.expectEmit(true, true, true, true);
emit GatePassed(passerbyA, gate);

vm.prank(gate);
freeForAllExcubia.pass(passerbyA, "");

vm.expectEmit(true, true, true, true);
emit GatePassed(passerbyB, gate);

vm.prank(gate);
freeForAllExcubia.pass(passerbyB, "");

assertTrue(freeForAllExcubia.passedPassersby(passerbyB));
}

/**
* Fuzz Tests
*/
function testFuzzSetGate(address _gate) external {
vm.assume(_gate != address(0));
vm.prank(owner);
freeForAllExcubia.setGate(_gate);
assertEq(freeForAllExcubia.gate(), _gate);
}

function testFuzzPass(address _gate, address _passerby) external {
vm.assume(_gate != address(0) && _passerby != address(0));
vm.prank(owner);
freeForAllExcubia.setGate(_gate);

vm.expectEmit(true, true, true, true);
emit GatePassed(_passerby, _gate);

vm.prank(_gate);
freeForAllExcubia.pass(_passerby, "");

assertTrue(freeForAllExcubia.passedPassersby(_passerby));
}
}
22 changes: 0 additions & 22 deletions packages/contracts/contracts/test/Lock.t.sol

This file was deleted.

9 changes: 9 additions & 0 deletions packages/contracts/ignition/modules/FreeForAllExcubia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"

const FreeForAllExcubiaModule = buildModule("FreeForAllExcubiaModule", (m) => {
const freeForAllExcubia = m.contract("FreeForAllExcubia")

return { freeForAllExcubia }
})

export default FreeForAllExcubiaModule
20 changes: 0 additions & 20 deletions packages/contracts/ignition/modules/Lock.ts

This file was deleted.

Loading

0 comments on commit fad47c5

Please sign in to comment.