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

Minimal proxy pattern for Checker / Policy contracts #21

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion packages/contracts/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
},
"packageManager": "yarn@4.5.0",
"dependencies": {
"@openzeppelin/contracts": "^5.0.2"
"@openzeppelin/contracts": "^5.0.2",
"solady": "^0.0.298"
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IAdvancedChecker, Check, CheckStatus} from "./interfaces/IAdvancedChecker.sol";
import {IAdvancedChecker, Check, CheckStatus} from "../interfaces/IAdvancedChecker.sol";
import {Checker} from "./Checker.sol";

/// @title AdvancedChecker.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IBaseChecker} from "./interfaces/IBaseChecker.sol";
import {IBaseChecker} from "../interfaces/IBaseChecker.sol";
import {Checker} from "./Checker.sol";

/// @title BaseChecker
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

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

/// @title Checker
/// @notice Abstract base contract for implementing attribute verification logic.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Factory} from "../utils/Factory.sol";

/// @title CheckerFactory
/// @notice Base factory for deploying Checker clones.
abstract contract CheckerFactory is Factory {
/// @notice Initializes checker factory with implementation.
/// @param _implementation Address of checker implementation.
constructor(address _implementation) Factory(_implementation) {}

/// @notice Deploys new checker clone with specific verifiers.
/// @param verifiers Array of verifier addresses for this instance.
function deploy(address[] calldata verifiers) external returns (address) {
return _deployClone(abi.encode(verifiers));
}
}
15 changes: 15 additions & 0 deletions packages/contracts/contracts/src/core/interfaces/IFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title IFactory
/// @notice Base interface for proxy factory contracts.
/// @dev Defines core functionality for clone deployment and event emit for tracking deployments.
interface IFactory {
/// @notice Signals successful clone deployment.
/// @param instance Address of newly deployed clone.
event CloneDeployed(address indexed instance);

/// @notice Gets the implementation contract address.
/// @return Address of the implementation contract.
function implementation() external view returns (address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
pragma solidity ^0.8.20;

import {Policy} from "./Policy.sol";
import {IAdvancedPolicy, Check} from "./interfaces/IAdvancedPolicy.sol";
import {AdvancedChecker, CheckStatus} from "./AdvancedChecker.sol";
import {IAdvancedPolicy, Check} from "../interfaces/IAdvancedPolicy.sol";
import {AdvancedChecker, CheckStatus} from "../checker/AdvancedChecker.sol";

/// @title AdvancedPolicy.
/// @notice Implements advanced policy checks with pre, main, and post validation stages.
Expand All @@ -23,8 +23,8 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy {
bool public immutable ALLOW_MULTIPLE_MAIN;

/// @notice Tracks validation status for each subject per target.
/// @dev Maps target => subject => CheckStatus.
mapping(address => mapping(address => CheckStatus)) public enforced;
/// @dev Maps subject => CheckStatus.
mapping(address => CheckStatus) public enforced;

/// @notice Initializes contract with an AdvancedChecker instance and checks configs.
/// @param _advancedChecker Address of the AdvancedChecker contract.
Expand Down Expand Up @@ -64,7 +64,7 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy {
revert UnsuccessfulCheck();
}

CheckStatus storage status = enforced[msg.sender][subject];
CheckStatus storage status = enforced[subject];

// Handle PRE check.
if (checkType == Check.PRE) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Factory} from "../utils/Factory.sol";
import {AdvancedChecker} from "../checker/AdvancedChecker.sol";

/// @title AdvancedPolicyFactory
/// @notice Factory for deploying advanced policy clones.
/// @dev Manages multi-phase policy deployments.
abstract contract AdvancedPolicyFactory is Factory {
/// @notice Initializes AdvancedPolicy factory with implementation.
/// @param _implementation Address of AdvancedPolicy implementation.
constructor(address _implementation) Factory(_implementation) {}

/// @notice Deploys new advanced policy with specific config.
/// @param checker AdvancedChecker address for this instance.
/// @param skipPre Skip pre-check flag.
/// @param skipPost Skip post-check flag.
/// @param allowMultipleMain Allow multiple main checks.
function deploy(
AdvancedChecker checker,
bool skipPre,
bool skipPost,
bool allowMultipleMain
) external returns (address) {
return
_deployClone(
bytes.concat(
abi.encodePacked(checker),
abi.encode(skipPre),
abi.encode(skipPost),
abi.encode(allowMultipleMain)
)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IBasePolicy} from "./interfaces/IBasePolicy.sol";
import {IBasePolicy} from "../interfaces/IBasePolicy.sol";
import {Policy} from "./Policy.sol";
import {BaseChecker} from "./BaseChecker.sol";
import {BaseChecker} from "../checker/BaseChecker.sol";

/// @title BasePolicy
/// @notice Abstract base contract for implementing specific policy checks.
Expand All @@ -18,8 +18,8 @@ abstract contract BasePolicy is Policy, IBasePolicy {
BaseChecker public immutable BASE_CHECKER;

/// @notice Tracks enforcement status for each subject per target.
/// @dev Maps target => subject => enforcement status.
mapping(address => mapping(address => bool)) public enforced;
/// @dev Maps subject => enforcement status.
mapping(address => bool) public enforced;

/// @notice Initializes the contract with a BaseChecker instance.
/// @param _baseChecker Address of the BaseChecker contract.
Expand Down Expand Up @@ -48,10 +48,10 @@ abstract contract BasePolicy is Policy, IBasePolicy {
function _enforce(address subject, bytes[] memory evidence) internal {
bool checked = BASE_CHECKER.check(subject, evidence);

if (enforced[msg.sender][subject]) revert AlreadyEnforced();
if (enforced[subject]) revert AlreadyEnforced();
if (!checked) revert UnsuccessfulCheck();

enforced[msg.sender][subject] = checked;
enforced[subject] = checked;

emit Enforced(subject, target, evidence);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Factory} from "../utils/Factory.sol";
import {BaseChecker} from "../checker/BaseChecker.sol";

/// @title BasePolicyFactory
/// @notice Factory for deploying basic policy clones.
/// @dev Manages single-check policy deployments.
abstract contract BasePolicyFactory is Factory {
/// @notice Initializes BasePolicy factory with implementation.
/// @param _implementation Address of BasePolicy implementation.
constructor(address _implementation) Factory(_implementation) {}

/// @notice Deploys new policy clone with specific BaseChecker.
/// @param checker BaseChecker address for this instance.
function deploy(BaseChecker checker) external returns (address) {
return _deployClone(abi.encode(checker));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
pragma solidity ^0.8.20;

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

/// @title Policy
/// @notice Implements a base policy contract that protects access to a target contract
/// @dev Inherits from OpenZeppelin's Ownable and implements IPolicy interface
/// @notice Implements a base policy contract that protects access to a target contract.
/// @dev Inherits from OpenZeppelin's Ownable and implements IPolicy interface.
///
/// This contract serves as a base for implementing specific policy checks that must be
/// satisfied before interacting with a protected target contract. It provides core
Expand Down
26 changes: 26 additions & 0 deletions packages/contracts/contracts/src/core/utils/Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {LibClone} from "solady/src/utils/LibClone.sol";
import {IFactory} from "../interfaces/IFactory.sol";

/// @title Factory
/// @notice Abstract base contract for implementing clone factories.
/// @dev Provides infrastructure for deploying minimal proxy clones with immutable args.
abstract contract Factory is IFactory {
/// @notice Implementation contract used as template for clones.
address public immutable IMPLEMENTATION;

/// @notice Sets the implementation contract address.
/// @param _implementation Address of the implementation contract.
constructor(address _implementation) {
IMPLEMENTATION = _implementation;
}

/// @notice Creates new clone with specific arguments.
/// @param args Configuration arguments for this specific clone.
function _deployClone(bytes memory args) internal returns (address instance) {
instance = LibClone.clone(IMPLEMENTATION, args);
emit CloneDeployed(instance);
}
}
8 changes: 4 additions & 4 deletions packages/contracts/contracts/src/test/Advanced.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol";
import {AdvancedVoting} from "./advanced/AdvancedVoting.sol";
import {AdvancedERC721CheckerHarness} from "./wrappers/AdvancedERC721CheckerHarness.sol";
import {AdvancedERC721PolicyHarness} from "./wrappers/AdvancedERC721PolicyHarness.sol";
import {IChecker} from "../interfaces/IChecker.sol";
import {IPolicy} from "../interfaces/IPolicy.sol";
import {IAdvancedPolicy} from "../interfaces/IAdvancedPolicy.sol";
import {IChecker} from "../core/interfaces/IChecker.sol";
import {IPolicy} from "../core/interfaces/IPolicy.sol";
import {IAdvancedPolicy} from "../core/interfaces/IAdvancedPolicy.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Check} from "../interfaces/IAdvancedChecker.sol";
import {Check} from "../core/interfaces/IAdvancedChecker.sol";

contract AdvancedChecker is Test {
NFT internal signupNft;
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts/contracts/src/test/Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {BaseERC721Policy} from "./base/BaseERC721Policy.sol";
import {BaseVoting} from "./base/BaseVoting.sol";
import {BaseERC721CheckerHarness} from "./wrappers/BaseERC721CheckerHarness.sol";
import {BaseERC721PolicyHarness} from "./wrappers/BaseERC721PolicyHarness.sol";
import {IPolicy} from "../interfaces/IPolicy.sol";
import {IChecker} from "../interfaces/IChecker.sol";
import {IPolicy} from "../core/interfaces/IPolicy.sol";
import {IChecker} from "../core/interfaces/IChecker.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {AdvancedChecker} from "../../AdvancedChecker.sol";
import {AdvancedChecker} from "../../core/checker/AdvancedChecker.sol";
import {BaseERC721Checker} from "../base/BaseERC721Checker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {AdvancedPolicy} from "../../AdvancedPolicy.sol";
import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol";
import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol";

/// @title AdvancedERC721Policy.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {AdvancedPolicy} from "../../AdvancedPolicy.sol";
import {Check} from "../../interfaces/IAdvancedPolicy.sol";
import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol";
import {Check} from "../../core/interfaces/IAdvancedPolicy.sol";

/// @title AdvancedVoting.
/// @notice Advanced voting system with NFT-based phases and eligibility verification.
Expand Down Expand Up @@ -67,7 +67,7 @@ contract AdvancedVoting {
/// - Token balance must meet requirements.
/// @custom:emits Voted when vote is recorded.
function vote(uint8 option) external {
(bool pre, , ) = POLICY.enforced(address(this), msg.sender);
(bool pre, , ) = POLICY.enforced(msg.sender);
if (!pre) revert NotRegistered();
if (option >= 2) revert InvalidOption();

Expand All @@ -92,7 +92,7 @@ contract AdvancedVoting {
/// - Caller must meet eligibility criteria (no existing benefits).
/// @custom:emits Eligible when verification succeeds.
function eligible() external {
(bool pre, uint8 main, bool post) = POLICY.enforced(address(this), msg.sender);
(bool pre, uint8 main, bool post) = POLICY.enforced(msg.sender);

if (!pre) revert NotRegistered();
if (main == 0) revert NotVoted();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {BaseChecker} from "../../../src/BaseChecker.sol";
import {BaseChecker} from "../../core/checker/BaseChecker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

/// @title BaseERC721Checker.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {BasePolicy} from "../../../src/BasePolicy.sol";
import {BasePolicy} from "../../core/policy/BasePolicy.sol";
import {BaseERC721Checker} from "./BaseERC721Checker.sol";

/// @title BaseERC721Policy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ contract BaseVoting {
/// @custom:emits Voted on successful vote cast.
function vote(uint8 option) external {
// Verify registration and voting status.
if (!POLICY.enforced(address(this), msg.sender)) revert NotRegistered();
if (!POLICY.enforced(msg.sender)) revert NotRegistered();
if (hasVoted[msg.sender]) revert AlreadyVoted();
if (option >= 2) revert InvalidOption();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.20;

import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol";
import {Check} from "../../interfaces/IAdvancedChecker.sol";
import {Check} from "../../core/interfaces/IAdvancedChecker.sol";

/// @title AdvancedERC721CheckerHarness.
/// @notice Test harness exposing internal methods of AdvancedERC721Checker.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity ^0.8.20;

import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol";
import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol";
import {Check} from "../../interfaces/IAdvancedChecker.sol";
import {Check} from "../../core/interfaces/IAdvancedChecker.sol";

/// @title AdvancedERC721PolicyHarness.
/// @notice Test harness for AdvancedERC721Policy internal methods.
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"typescript": "5.3"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.2"
"@openzeppelin/contracts": "^5.0.2",
"solady": "^0.0.298"
}
}
3 changes: 2 additions & 1 deletion packages/contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
@openzeppelin/=node_modules/@openzeppelin/
forge-std/=node_modules/forge-std/
@solady/=node_modules/solady/
forge-std/=node_modules/forge-std/
Loading
Loading