-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): init porting of excubia core abstract contract and F…
…reeForAll extension
- Loading branch information
Showing
11 changed files
with
437 additions
and
180 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
42
packages/contracts/contracts/src/extensions/FreeForAllExcubia.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
147
packages/contracts/contracts/test/FreeForAllExcubia.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.