diff --git a/packages/contracts/contracts/package.json b/packages/contracts/contracts/package.json index ca52c03..d5551d9 100644 --- a/packages/contracts/contracts/package.json +++ b/packages/contracts/contracts/package.json @@ -27,6 +27,7 @@ }, "packageManager": "yarn@4.5.0", "dependencies": { - "@openzeppelin/contracts": "^5.0.2" + "@openzeppelin/contracts": "^5.0.2", + "solady": "^0.0.298" } } diff --git a/packages/contracts/contracts/src/AdvancedChecker.sol b/packages/contracts/contracts/src/AdvancedChecker.sol deleted file mode 100644 index 922b245..0000000 --- a/packages/contracts/contracts/src/AdvancedChecker.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IAdvancedChecker, Check, CheckStatus} from "./interfaces/IAdvancedChecker.sol"; -import {Checker} from "./Checker.sol"; - -/// @title AdvancedChecker. -/// @notice Multi-phase validation checker with pre, main, and post checks. -/// @dev Base contract for implementing complex validation logic with configurable check phases. -abstract contract AdvancedChecker is IAdvancedChecker, Checker { - constructor(address[] memory _verifiers) Checker(_verifiers) {} - - /// @notice Entry point for validation checks. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check (PRE, MAIN, POST). - /// @return checked Validation result. - function check( - address subject, - bytes[] calldata evidence, - Check checkType - ) external view override returns (bool checked) { - return _check(subject, evidence, checkType); - } - - /// @notice Core validation logic router. - /// @dev Directs to appropriate check based on type and configuration. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check type to perform. - /// @return checked Validation result. - function _check(address subject, bytes[] calldata evidence, Check checkType) internal view returns (bool checked) { - if (checkType == Check.PRE) { - return _checkPre(subject, evidence); - } - - if (checkType == Check.POST) { - return _checkPost(subject, evidence); - } - - return _checkMain(subject, evidence); - } - - /// @notice Pre-condition validation implementation. - /// @dev Override to implement pre-check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. - function _checkPre(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} - - /// @notice Main validation implementation. - /// @dev Override to implement main check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. - function _checkMain(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} - - /// @notice Post-condition validation implementation. - /// @dev Override to implement post-check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. - function _checkPost(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} -} diff --git a/packages/contracts/contracts/src/AdvancedPolicy.sol b/packages/contracts/contracts/src/AdvancedPolicy.sol deleted file mode 100644 index 3d8ccde..0000000 --- a/packages/contracts/contracts/src/AdvancedPolicy.sol +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Policy} from "./Policy.sol"; -import {IAdvancedPolicy, Check} from "./interfaces/IAdvancedPolicy.sol"; -import {AdvancedChecker, CheckStatus} from "./AdvancedChecker.sol"; - -/// @title AdvancedPolicy. -/// @notice Implements advanced policy checks with pre, main, and post validation stages. -/// @dev Extends Policy contract with multi-stage validation capabilities. -abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { - /// @notice Reference to the validation checker contract. - /// @dev Immutable to ensure checker cannot be changed after deployment. - AdvancedChecker public immutable ADVANCED_CHECKER; - - /// @notice Controls whether pre-condition checks are required. - bool public immutable SKIP_PRE; - - /// @notice Controls whether post-condition checks are required. - bool public immutable SKIP_POST; - - /// @notice Controls whether main check can be executed multiple times. - 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; - - /// @notice Initializes contract with an AdvancedChecker instance and checks configs. - /// @param _advancedChecker Address of the AdvancedChecker contract. - /// @param _skipPre Skip pre-condition validation. - /// @param _skipPost Skip post-condition validation. - /// @param _allowMultipleMain Allow multiple main validations. - constructor(AdvancedChecker _advancedChecker, bool _skipPre, bool _skipPost, bool _allowMultipleMain) { - ADVANCED_CHECKER = _advancedChecker; - SKIP_PRE = _skipPre; - SKIP_POST = _skipPost; - ALLOW_MULTIPLE_MAIN = _allowMultipleMain; - } - - /// @notice Enforces policy check for a subject. - /// @dev Only callable by target contract. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check (PRE, MAIN, POST). - function enforce(address subject, bytes[] calldata evidence, Check checkType) external override onlyTarget { - _enforce(subject, evidence, checkType); - } - - /// @notice Internal check enforcement logic. - /// @dev Handles different check types and their dependencies. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check to perform. - /// @custom:throws CannotPreCheckWhenSkipped If PRE check attempted when skipped. - /// @custom:throws CannotPostCheckWhenSkipped If POST check attempted when skipped. - /// @custom:throws UnsuccessfulCheck If validation fails. - /// @custom:throws AlreadyEnforced If check was already completed. - /// @custom:throws PreCheckNotEnforced If PRE check is required but not done. - /// @custom:throws MainCheckNotEnforced If MAIN check is required but not done. - /// @custom:throws MainCheckAlreadyEnforced If multiple MAIN checks not allowed. - function _enforce(address subject, bytes[] calldata evidence, Check checkType) internal { - if (!ADVANCED_CHECKER.check(subject, evidence, checkType)) { - revert UnsuccessfulCheck(); - } - - CheckStatus storage status = enforced[msg.sender][subject]; - - // Handle PRE check. - if (checkType == Check.PRE) { - if (SKIP_PRE) revert CannotPreCheckWhenSkipped(); - if (status.pre) { - revert AlreadyEnforced(); - } - - status.pre = true; - } else if (checkType == Check.POST) { - // Handle POST check. - if (SKIP_POST) revert CannotPostCheckWhenSkipped(); - if (status.main == 0) { - revert MainCheckNotEnforced(); - } - if (status.post) { - revert AlreadyEnforced(); - } - status.post = true; - } else { - // Handle MAIN check. - if (!SKIP_PRE && !status.pre) { - revert PreCheckNotEnforced(); - } - if (!ALLOW_MULTIPLE_MAIN && status.main > 0) { - revert MainCheckAlreadyEnforced(); - } - status.main += 1; - } - - emit Enforced(subject, target, evidence, checkType); - } -} diff --git a/packages/contracts/contracts/src/BaseChecker.sol b/packages/contracts/contracts/src/BaseChecker.sol deleted file mode 100644 index bbb106b..0000000 --- a/packages/contracts/contracts/src/BaseChecker.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IBaseChecker} from "./interfaces/IBaseChecker.sol"; -import {Checker} from "./Checker.sol"; - -/// @title BaseChecker -/// @notice Abstract base contract for implementing validation checks. -/// @dev Provides a standardized interface for implementing custom validation logic -/// through the internal _check method. -abstract contract BaseChecker is Checker, IBaseChecker { - constructor(address[] memory _verifiers) Checker(_verifiers) {} - - /// @notice Validates evidence for a given subject address. - /// @dev External view function that delegates to internal _check implementation. - /// @param subject Address to validate. - /// @param evidence Custom validation data. - /// @return checked Boolean indicating if the check passed. - function check(address subject, bytes[] calldata evidence) external view override returns (bool checked) { - return _check(subject, evidence); - } - - /// @notice Internal validation logic implementation. - /// @dev Must be implemented by derived contracts. - /// @param subject Address to validate. - /// @param evidence Custom validation data. - /// @return checked Boolean indicating if the check passed. - function _check(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} -} diff --git a/packages/contracts/contracts/src/BasePolicy.sol b/packages/contracts/contracts/src/BasePolicy.sol deleted file mode 100644 index c9e9c5d..0000000 --- a/packages/contracts/contracts/src/BasePolicy.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IBasePolicy} from "./interfaces/IBasePolicy.sol"; -import {Policy} from "./Policy.sol"; -import {BaseChecker} from "./BaseChecker.sol"; - -/// @title BasePolicy -/// @notice Abstract base contract for implementing specific policy checks. -/// @dev Inherits from Policy and implements IBasePolicy interface. -/// -/// Provides core functionality for enforcing policy checks through a BaseChecker -/// contract. Each specific policy implementation should extend this contract -/// and implement its custom checking logic. -abstract contract BasePolicy is Policy, IBasePolicy { - /// @notice Reference to the BaseChecker contract used for validation. - /// @dev Immutable to ensure checker cannot be changed after deployment. - 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; - - /// @notice Initializes the contract with a BaseChecker instance. - /// @param _baseChecker Address of the BaseChecker contract. - /// @dev The BaseChecker address cannot be changed after deployment. - constructor(BaseChecker _baseChecker) { - BASE_CHECKER = _baseChecker; - } - - /// @notice External function to enforce policy checks. - /// @dev Only callable by the target contract. - /// @param subject Address to enforce the check on. - /// @param evidence Additional data required for verification. - /// @custom:throws AlreadyEnforced if check was previously enforced. - /// @custom:throws UnsuccessfulCheck if the check fails. - /// @custom:emits Enforced when check succeeds. - function enforce(address subject, bytes[] calldata evidence) external override onlyTarget { - _enforce(subject, evidence); - } - - /// @notice Internal implementation of enforcement logic. - /// @dev Performs the actual check using BASE_CHECKER. - /// @param subject Address to enforce the check on. - /// @param evidence Additional data required for verification. - /// @custom:throws AlreadyEnforced if already enforced for this subject. - /// @custom:throws UnsuccessfulCheck if BASE_CHECKER.check returns false. - function _enforce(address subject, bytes[] memory evidence) internal { - bool checked = BASE_CHECKER.check(subject, evidence); - - if (enforced[msg.sender][subject]) revert AlreadyEnforced(); - if (!checked) revert UnsuccessfulCheck(); - - enforced[msg.sender][subject] = checked; - - emit Enforced(subject, target, evidence); - } -} diff --git a/packages/contracts/contracts/src/Checker.sol b/packages/contracts/contracts/src/Checker.sol deleted file mode 100644 index 83cc4a6..0000000 --- a/packages/contracts/contracts/src/Checker.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IChecker} from "./interfaces/IChecker.sol"; - -/// @title Checker -/// @notice Abstract base contract for implementing attribute verification logic. -/// @dev Provides infrastructure to orchestrate third-party verifiers for single checks. -abstract contract Checker is IChecker { - /// @notice Array of third-party contract addresses used for verification. - /// @dev Can include existing and already deployed Checkers, NFTs, MACI polls, and/or any other contract - /// that provides evidence verification. These contracts should already be deployed and operational. - address[] internal verifiers; - - /// @notice Initializes the Checker with an optional list of third-party verification contracts. - /// @param _verifiers Array of addresses for existing verification contracts. - /// @dev Each address should point to a deployed contract that will be consulted during verification. - /// This array can remain empty if there's no reliance on external verifiers. - constructor(address[] memory _verifiers) { - verifiers = _verifiers; - } - - /// @notice Retrieves the verifier address at a specific index. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function getVerifierAtIndex(uint256 index) external view returns (address) { - return _getVerifierAtIndex(index); - } - - /// @notice Internal implementation of verifier address retrieval at a specific index. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function _getVerifierAtIndex(uint256 index) internal view returns (address) { - if (index >= verifiers.length) revert VerifierNotFound(); - - return verifiers[index]; - } -} diff --git a/packages/contracts/contracts/src/Policy.sol b/packages/contracts/contracts/src/Policy.sol deleted file mode 100644 index 40234cf..0000000 --- a/packages/contracts/contracts/src/Policy.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {Ownable} from "@openzeppelin/contracts/access/Ownable.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 -/// -/// 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 -/// functionality for managing the protected target address and access control. -abstract contract Policy is IPolicy, Ownable(msg.sender) { - /// @notice The policy-protected contract address. - /// @dev This address can only be set once by the owner. - /// For example, the target is a Semaphore group that requires the subject - /// to meet certain criteria in order to join the group. - address internal target; - - /// @notice Restricts function access to only the target contract. - /// @dev Throws TargetOnly error if called by any other address. - modifier onlyTarget() { - if (msg.sender != target) revert TargetOnly(); - _; - } - - /// @notice Sets the target contract address. - /// @dev Can only be called once by the owner. - /// @param _target Address of the contract to be protected by this policy. - /// @custom:throws ZeroAddress if _target is the zero address. - /// @custom:throws TargetAlreadySet if target has already been set. - /// @custom:emits TargetSet when target is successfully set. - function setTarget(address _target) external virtual onlyOwner { - if (_target == address(0)) revert ZeroAddress(); - if (target != address(0)) revert TargetAlreadySet(); - - target = _target; - - emit TargetSet(_target); - } - - /// @notice Retrieves the current target contract address. - /// @return address The address of the policy-protected contract. - /// @dev Returns zero address if target hasn't been set yet. - function getTarget() public view returns (address) { - return target; - } -} diff --git a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol new file mode 100644 index 0000000..b89c752 --- /dev/null +++ b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IAdvancedChecker, Check, CheckStatus} from "../interfaces/IAdvancedChecker.sol"; +import {Clone} from "../proxy/Clone.sol"; + +/// @title AdvancedChecker +/// @notice Abstract contract for multi-phase validation (PRE, MAIN, POST). +/// @dev Implements advanced validation by routing checks to appropriate phases. +/// This is intended to be extended for complex validation systems. +abstract contract AdvancedChecker is Clone, IAdvancedChecker { + /// @notice Validates a subject's evidence for a specific check phase. + /// @dev External entry point for validation checks, delegating logic to `_check`. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute (PRE, MAIN, POST). + /// @return checked Boolean indicating whether the validation passed. + function check( + address subject, + bytes[] calldata evidence, + Check checkType + ) external view override returns (bool checked) { + return _check(subject, evidence, checkType); + } + + /// @notice Core validation logic dispatcher. + /// @dev Routes validation calls to specific phase methods (_checkPre, _checkMain, _checkPost). + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute. + /// @return checked Boolean indicating whether the validation passed. + function _check(address subject, bytes[] calldata evidence, Check checkType) internal view returns (bool checked) { + if (checkType == Check.PRE) { + return _checkPre(subject, evidence); + } + + if (checkType == Check.POST) { + return _checkPost(subject, evidence); + } + + return _checkMain(subject, evidence); + } + + /// @notice Pre-condition validation logic. + /// @dev Derived contracts should override this to implement pre-check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function _checkPre(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} + + /// @notice Main validation logic. + /// @dev Derived contracts should override this to implement main check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function _checkMain(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} + + /// @notice Post-condition validation logic. + /// @dev Derived contracts should override this to implement post-check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function _checkPost(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} +} diff --git a/packages/contracts/contracts/src/core/checker/BaseChecker.sol b/packages/contracts/contracts/src/core/checker/BaseChecker.sol new file mode 100644 index 0000000..0b3bdf2 --- /dev/null +++ b/packages/contracts/contracts/src/core/checker/BaseChecker.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBaseChecker} from "../interfaces/IBaseChecker.sol"; +import {Clone} from "../proxy/Clone.sol"; + +/// @title BaseChecker +/// @notice Abstract base contract for implementing validation checks. +/// @dev This contract provides a standardized interface for validation logic, delegating +/// actual implementation to the internal `_check` method. It is clone-compatible. +abstract contract BaseChecker is Clone, IBaseChecker { + /// @notice Validates a subject's evidence. + /// @dev External view function that calls the `_check` method, allowing derived contracts + /// to implement custom validation logic. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function check(address subject, bytes[] calldata evidence) external view override returns (bool checked) { + return _check(subject, evidence); + } + + /// @notice Internal validation logic implementation. + /// @dev Must be overridden by derived contracts to define custom validation rules. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function _check(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} +} diff --git a/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol new file mode 100644 index 0000000..13a9441 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title Check +/// @notice Enum representing validation phases. +/// @dev Used to identify the specific validation phase in multi-phase systems. +/// @dev The PRE and POST checks are optional and may be skipped based on contract settings. +enum Check { + /// Pre-condition validation. + PRE, + /// Primary validation. + MAIN, + /// Post-condition validation. + POST +} + +/// @notice Tracks the status of validation checks. +/// @dev Used in AdvancedPolicy to maintain the state of multi-phase checks. +struct CheckStatus { + /// @notice Indicates whether the pre-condition check has been completed. + bool pre; + /// @notice Tracks the number of main checks completed. + uint8 main; + /// @notice Indicates whether the post-condition check has been completed. + bool post; +} + +/// @title IAdvancedChecker +/// @notice Interface defining multi-phase validation capabilities. +/// @dev Supports PRE, MAIN, and POST validation phases. +interface IAdvancedChecker { + /// @notice Validates a subject for a specific check phase. + /// @dev Implementations should route to appropriate phase-specific logic. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute (PRE, MAIN, POST). + /// @return checked Boolean indicating whether the validation passed. + function check(address subject, bytes[] calldata evidence, Check checkType) external view returns (bool checked); +} diff --git a/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol new file mode 100644 index 0000000..62a3626 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IPolicy} from "./IPolicy.sol"; +import {Check} from "./IAdvancedChecker.sol"; + +/// @title IAdvancedPolicy +/// @notice Extends IPolicy with support for multi-phase validation checks (pre, main, post). +/// @dev Adds granular error reporting and event logging for advanced enforcement scenarios. +interface IAdvancedPolicy is IPolicy { + /// @notice Error thrown when multiple main checks are attempted but not allowed. + error MainCheckAlreadyEnforced(); + + /// @notice Error thrown when a main check is attempted without a prior pre-check. + error PreCheckNotEnforced(); + + /// @notice Error thrown when a post-check is attempted without a prior main check. + error MainCheckNotEnforced(); + + /// @notice Error thrown when a pre-check is attempted while pre-checks are skipped. + error CannotPreCheckWhenSkipped(); + + /// @notice Error thrown when a post-check is attempted while post-checks are skipped. + error CannotPostCheckWhenSkipped(); + + /// @notice Emitted when a subject successfully passes a validation check. + /// @param subject Address that passed the validation. + /// @param target Address of the protected contract. + /// @param evidence Data used during validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). + event Enforced(address indexed subject, address indexed target, bytes[] evidence, Check checkType); + + /// @notice Enforces a specific phase of the policy check on a given subject. + /// @dev Delegates validation logic to the corresponding phase's check method. + /// @param subject Address to validate. + /// @param evidence Data required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). + function enforce(address subject, bytes[] calldata evidence, Check checkType) external; +} diff --git a/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol b/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol new file mode 100644 index 0000000..299b520 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title IBaseChecker +/// @notice Interface defining base validation functionality for policies. +/// @dev Contracts implementing this interface must define the `check` method. +interface IBaseChecker { + /// @notice Validates a subject against provided evidence. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. + function check(address subject, bytes[] calldata evidence) external view returns (bool checked); +} diff --git a/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol b/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol new file mode 100644 index 0000000..9fb1a6a --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IPolicy} from "./IPolicy.sol"; + +/// @title IBasePolicy +/// @notice Extends IPolicy with basic validation and enforcement capabilities. +/// @dev Adds event logging and a method to enforce policy checks. +interface IBasePolicy is IPolicy { + /// @notice Emitted when a subject successfully passes a policy enforcement check. + /// @param subject Address that passed the validation. + /// @param target Address of the protected contract. + /// @param evidence Data used during validation. + event Enforced(address indexed subject, address indexed target, bytes[] evidence); + + /// @notice Enforces a validation check on a given subject. + /// @dev This method ensures that the provided subject meets the policy's criteria. + /// @param subject Address to validate. + /// @param evidence Data required for validation. + function enforce(address subject, bytes[] calldata evidence) external; +} diff --git a/packages/contracts/contracts/src/core/interfaces/IClone.sol b/packages/contracts/contracts/src/core/interfaces/IClone.sol new file mode 100644 index 0000000..ae70942 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IClone.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title IClone +/// @notice Interface for cloneable contracts with initialization logic. +/// @dev Supports minimal proxy pattern and appended bytes retrieval. +interface IClone { + /// @notice Error thrown when the clone is already initialized. + error AlreadyInitialized(); + + /// @notice Initializes the clone contract. + /// @dev Typically used for setting up state or configuration data. + function initialize() external; + + /// @notice Retrieves appended bytes from the clone's runtime bytecode. + /// @return Appended bytes passed during the clone's creation. + function getAppendedBytes() external returns (bytes memory); +} diff --git a/packages/contracts/contracts/src/core/interfaces/IFactory.sol b/packages/contracts/contracts/src/core/interfaces/IFactory.sol new file mode 100644 index 0000000..3be8285 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IFactory.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title IFactory +/// @notice Base interface for factory contracts responsible for deploying minimal proxy clones. +/// @dev Provides methods for clone deployment and related events. +interface IFactory { + /// @notice Emitted when a new clone contract is successfully deployed. + /// @param clone Address of the deployed clone contract. + event CloneDeployed(address indexed clone); +} diff --git a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol new file mode 100644 index 0000000..4c1afe2 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @title IPolicy +/// @notice Core interface for managing policies that protect specific contracts. +/// @dev Provides methods for setting and retrieving the protected contract and enforcing checks. +interface IPolicy { + /// @notice Emitted when the target contract is successfully set. + /// @param target Address of the protected contract. + event TargetSet(address indexed target); + + /// @notice Error thrown when a zero address is provided where not allowed. + error ZeroAddress(); + + /// @notice Error thrown when a validation check fails. + error UnsuccessfulCheck(); + + /// @notice Error thrown when the target contract is not set. + error TargetNotSet(); + + /// @notice Error thrown when a function is restricted to calls from the target contract. + error TargetOnly(); + + /// @notice Error thrown when attempting to set the target more than once. + error TargetAlreadySet(); + + /// @notice Error thrown when a subject is already enforced. + error AlreadyEnforced(); + + /// @notice Retrieves the policy trait identifier. + /// @dev This is typically used to distinguish policy implementations (e.g., "Semaphore"). + /// @return The policy trait string. + function trait() external pure returns (string memory); + + /// @notice Sets the contract address to be protected by this policy. + /// @dev This function is restricted to the owner and can only be called once. + /// @param _target The address of the protected contract. + function setTarget(address _target) external; +} diff --git a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol new file mode 100644 index 0000000..dd2cf32 --- /dev/null +++ b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IAdvancedPolicy, Check} from "../interfaces/IAdvancedPolicy.sol"; +import {AdvancedChecker, CheckStatus} from "../checker/AdvancedChecker.sol"; +import {Policy} from "./Policy.sol"; + +/// @title AdvancedPolicy +/// @notice Implements multi-stage policy checks with pre, main, and post validation stages. +/// @dev Extends Policy and provides advanced enforcement logic with an AdvancedChecker. +abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { + /// @notice Reference to the AdvancedChecker contract used for validation. + AdvancedChecker public ADVANCED_CHECKER; + + /// @notice Controls whether pre-condition checks are required. + bool public SKIP_PRE; + + /// @notice Controls whether post-condition checks are required. + bool public SKIP_POST; + + /// @notice Controls whether main check can be executed multiple times. + bool public ALLOW_MULTIPLE_MAIN; + + /// @notice Tracks enforcement status for each subject for each phase. + mapping(address => CheckStatus) public enforced; + + /// @notice Initializes the contract with appended bytes data for configuration. + /// @dev Decodes AdvancedChecker address and sets the owner. + function _initialize() internal virtual override { + super._initialize(); + + bytes memory data = _getAppendedBytes(); + (address sender, address advCheckerAddr, bool skipPre, bool skipPost, bool allowMultipleMain) = abi.decode( + data, + (address, address, bool, bool, bool) + ); + + _transferOwnership(sender); + + ADVANCED_CHECKER = AdvancedChecker(advCheckerAddr); + SKIP_PRE = skipPre; + SKIP_POST = skipPost; + ALLOW_MULTIPLE_MAIN = allowMultipleMain; + } + + /// @notice Enforces a multi-stage policy check. + /// @dev Handles pre, main, and post validation stages. Only callable by the target contract. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). + function enforce(address subject, bytes[] calldata evidence, Check checkType) external override onlyTarget { + _enforce(subject, evidence, checkType); + } + + /// @notice Internal implementation of multi-stage enforcement logic. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). + function _enforce(address subject, bytes[] calldata evidence, Check checkType) internal { + CheckStatus storage status = enforced[subject]; + + if (checkType == Check.PRE) { + if (SKIP_PRE) revert CannotPreCheckWhenSkipped(); + if (status.pre) revert AlreadyEnforced(); + status.pre = true; + } else if (checkType == Check.POST) { + if (SKIP_POST) revert CannotPostCheckWhenSkipped(); + if (status.main == 0) revert MainCheckNotEnforced(); + if (status.post) revert AlreadyEnforced(); + status.post = true; + } else { + if (!SKIP_PRE && !status.pre) revert PreCheckNotEnforced(); + if (!ALLOW_MULTIPLE_MAIN && status.main > 0) revert MainCheckAlreadyEnforced(); + status.main += 1; + } + + if (!ADVANCED_CHECKER.check(subject, evidence, checkType)) revert UnsuccessfulCheck(); + + emit Enforced(subject, target, evidence, checkType); + } +} diff --git a/packages/contracts/contracts/src/core/policy/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol new file mode 100644 index 0000000..4af62d2 --- /dev/null +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IBasePolicy} from "../interfaces/IBasePolicy.sol"; +import {Policy} from "./Policy.sol"; +import {BaseChecker} from "../checker/BaseChecker.sol"; + +/// @title BasePolicy +/// @notice Abstract base contract for implementing custom policies using a BaseChecker. +/// @dev Extends Policy and provides enforcement logic using a BaseChecker instance. +abstract contract BasePolicy is Policy, IBasePolicy { + /// @notice Reference to the BaseChecker contract used for validation. + BaseChecker public BASE_CHECKER; + + /// @notice Tracks enforcement status for each subject. + mapping(address => bool) public enforced; + + /// @notice Initializes the contract with appended bytes data for configuration. + /// @dev Decodes BaseChecker address and sets the owner. + function _initialize() internal virtual override { + super._initialize(); + + bytes memory data = _getAppendedBytes(); + (address sender, address baseCheckerAddr) = abi.decode(data, (address, address)); + + _transferOwnership(sender); + + BASE_CHECKER = BaseChecker(baseCheckerAddr); + } + + /// @notice Enforces a policy check for a subject. + /// @dev Uses the BaseChecker for validation logic. Only callable by the target contract. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + function enforce(address subject, bytes[] calldata evidence) external override onlyTarget { + _enforce(subject, evidence); + } + + /// @notice Internal logic for enforcing policy checks. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + function _enforce(address subject, bytes[] memory evidence) internal { + if (enforced[subject]) revert AlreadyEnforced(); + if (!BASE_CHECKER.check(subject, evidence)) revert UnsuccessfulCheck(); + + enforced[subject] = true; + + emit Enforced(subject, target, evidence); + } +} diff --git a/packages/contracts/contracts/src/core/policy/Policy.sol b/packages/contracts/contracts/src/core/policy/Policy.sol new file mode 100644 index 0000000..9a7ebbe --- /dev/null +++ b/packages/contracts/contracts/src/core/policy/Policy.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IPolicy} from "../interfaces/IPolicy.sol"; +import {Clone} from "../proxy/Clone.sol"; + +/// @title Policy +/// @notice Abstract base contract for implementing policies to enforce access control. +/// @dev Extends Clone and Ownable to provide policy initialization, ownership, and target management. +abstract contract Policy is Clone, IPolicy, Ownable(msg.sender) { + /// @notice The address of the contract being protected by the policy. + /// @dev Can only be set once by the owner. + address public target; + + /// @notice Modifier to restrict access to only the target contract. + modifier onlyTarget() { + if (msg.sender != target) revert TargetOnly(); + _; + } + + /// @notice Initializes the contract and sets the owner. + /// @dev Overrides Clone's `_initialize` to include owner setup. + function _initialize() internal virtual override { + super._initialize(); + + // Sets the factory as the initial owner. + _transferOwnership(msg.sender); + } + + /// @notice Sets the contract address to be protected by this policy. + /// @dev Can only be called once by the owner. + /// @param _target The contract address to protect. + function setTarget(address _target) external virtual onlyOwner { + if (_target == address(0)) revert ZeroAddress(); + if (target != address(0)) revert TargetAlreadySet(); + + target = _target; + emit TargetSet(_target); + } +} diff --git a/packages/contracts/contracts/src/core/proxy/Clone.sol b/packages/contracts/contracts/src/core/proxy/Clone.sol new file mode 100644 index 0000000..1a5d2c4 --- /dev/null +++ b/packages/contracts/contracts/src/core/proxy/Clone.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IClone} from "../interfaces/IClone.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + +/// @title Clone +/// @notice Abstract base contract for creating cloneable contracts with initialization logic. +/// @dev Provides utilities for managing clone initialization and retrieving appended arguments. +abstract contract Clone is IClone { + /// @notice Tracks whether the clone has been initialized. + /// @dev Prevents re-initialization through the `_initialize` function. + bool public initialized; + + /// @notice Initializes the clone. + /// @dev Calls the internal `_initialize` function to set up the clone. + /// Reverts if the clone is already initialized. + function initialize() external { + _initialize(); + } + + /// @notice Retrieves appended arguments from the clone. + /// @dev Leverages `LibClone` to extract arguments from the clone's runtime bytecode. + /// @return appendedBytes The appended bytes extracted from the clone. + function getAppendedBytes() external returns (bytes memory appendedBytes) { + return _getAppendedBytes(); + } + + /// @notice Internal function to initialize the clone. + /// @dev Must be overridden by derived contracts to implement custom initialization logic. + /// Reverts if the clone has already been initialized. + function _initialize() internal virtual { + if (initialized) revert AlreadyInitialized(); + initialized = true; + } + + /// @notice Internal function to retrieve appended arguments from the clone. + /// @dev Uses `LibClone` utility to extract the arguments. + /// @return appendedBytes The appended bytes extracted from the clone. + function _getAppendedBytes() internal virtual returns (bytes memory appendedBytes) { + return LibClone.argsOnClone(address(this)); + } +} diff --git a/packages/contracts/contracts/src/core/proxy/Factory.sol b/packages/contracts/contracts/src/core/proxy/Factory.sol new file mode 100644 index 0000000..dacbcd4 --- /dev/null +++ b/packages/contracts/contracts/src/core/proxy/Factory.sol @@ -0,0 +1,31 @@ +// 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 deploying clone contracts. +/// @dev Provides functionality to deploy minimal proxy contracts using a standard implementation address. +abstract contract Factory is IFactory { + /// @notice Address of the implementation contract used for cloning. + /// @dev This address is immutable and defines the logic contract for all clones deployed by the factory. + address public immutable IMPLEMENTATION; + + /// @notice Initializes the factory with the implementation contract address. + /// @param _implementation Address of the logic contract to use for clones. + constructor(address _implementation) { + IMPLEMENTATION = _implementation; + } + + /// @notice Deploys a new clone contract. + /// @dev Uses `LibClone` to deploy a minimal proxy contract with appended initialization data. + /// Emits a `CloneDeployed` event upon successful deployment. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. + function _deploy(bytes memory data) internal returns (address clone) { + clone = LibClone.clone(IMPLEMENTATION, data); + + emit CloneDeployed(clone); + } +} diff --git a/packages/contracts/contracts/src/interfaces/IAdvancedChecker.sol b/packages/contracts/contracts/src/interfaces/IAdvancedChecker.sol deleted file mode 100644 index 67d27e1..0000000 --- a/packages/contracts/contracts/src/interfaces/IAdvancedChecker.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IChecker} from "./IChecker.sol"; - -/// @title Check. -/// @notice Defines validation phases in the AdvancedChecker system. -/// @custom:values PRE - Pre-condition validation. -/// MAIN - Primary validation. -/// POST - Post-condition validation. -enum Check { - PRE, - MAIN, - POST -} - -/// @notice Tracks validation status for pre, main, and post checks. -/// @dev Used to maintain check state in AdvancedPolicy. -struct CheckStatus { - /// @dev Pre-check completion status. - bool pre; - /// @dev Number of completed main checks. - uint8 main; - /// @dev Post-check completion status. - bool post; -} - -/// @title IAdvancedChecker. -/// @notice Defines multi-phase validation system interface. -/// @dev Implement this for custom validation logic with pre/main/post checks. -interface IAdvancedChecker is IChecker { - /// @notice Validates subject against specified check type. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check phase to execute. - /// @return checked True if validation passes. - function check(address subject, bytes[] calldata evidence, Check checkType) external view returns (bool checked); -} diff --git a/packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol b/packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol deleted file mode 100644 index d9f719d..0000000 --- a/packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IPolicy} from "./IPolicy.sol"; -import {Check} from "./IAdvancedChecker.sol"; - -/// @title IAdvancedPolicy. -/// @notice Extends IPolicy with multi-phase validation capabilities. -interface IAdvancedPolicy is IPolicy { - /// @notice Thrown when multiple main checks not allowed. - error MainCheckAlreadyEnforced(); - - /// @notice Thrown when main check attempted before pre-check. - error PreCheckNotEnforced(); - - /// @notice Thrown when post check attempted before main check. - error MainCheckNotEnforced(); - - /// @notice Thrown when pre-check validation attempted while skipped. - error CannotPreCheckWhenSkipped(); - - /// @notice Thrown when post-check validation attempted while skipped. - error CannotPostCheckWhenSkipped(); - - /// @notice Emitted when validation check succeeds. - /// @param subject Address that passed validation. - /// @param target Protected contract address. - /// @param evidence Validation data. - /// @param checkType Type of check performed. - event Enforced(address indexed subject, address indexed target, bytes[] evidence, Check checkType); - - /// @notice Enforces validation check on subject. - /// @dev Delegates to appropriate check method based on checkType. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check phase to execute. - function enforce(address subject, bytes[] calldata evidence, Check checkType) external; -} diff --git a/packages/contracts/contracts/src/interfaces/IBaseChecker.sol b/packages/contracts/contracts/src/interfaces/IBaseChecker.sol deleted file mode 100644 index d2f923d..0000000 --- a/packages/contracts/contracts/src/interfaces/IBaseChecker.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IChecker} from "./IChecker.sol"; - -/// @title IBaseChecker. -/// @notice Defines base validation functionality. -interface IBaseChecker is IChecker { - /// @notice Validates subject against evidence. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked True if validation passes. - function check(address subject, bytes[] calldata evidence) external view returns (bool checked); -} diff --git a/packages/contracts/contracts/src/interfaces/IBasePolicy.sol b/packages/contracts/contracts/src/interfaces/IBasePolicy.sol deleted file mode 100644 index faef64e..0000000 --- a/packages/contracts/contracts/src/interfaces/IBasePolicy.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IPolicy} from "./IPolicy.sol"; - -/// @title IBasePolicy. -/// @notice Extends IPolicy with basic validation capabilities. -interface IBasePolicy is IPolicy { - /// @notice Emitted when validation succeeds. - /// @param subject Address that passed validation. - /// @param target Protected contract address. - /// @param evidence Validation data. - event Enforced(address indexed subject, address indexed target, bytes[] evidence); - - /// @notice Enforces validation check on subject. - /// @param subject Address to validate. - /// @param evidence Validation data. - function enforce(address subject, bytes[] calldata evidence) external; -} diff --git a/packages/contracts/contracts/src/interfaces/IChecker.sol b/packages/contracts/contracts/src/interfaces/IChecker.sol deleted file mode 100644 index f8898a9..0000000 --- a/packages/contracts/contracts/src/interfaces/IChecker.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/// @title IChecker -/// @notice Core checker interface for attribute verification functionalities. -interface IChecker { - /// @notice Core error conditions. - error VerifierNotFound(); - - /// @notice Retrieves the verifier address at a specific index. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function getVerifierAtIndex(uint256 index) external view returns (address); -} diff --git a/packages/contracts/contracts/src/interfaces/IPolicy.sol b/packages/contracts/contracts/src/interfaces/IPolicy.sol deleted file mode 100644 index 9541a64..0000000 --- a/packages/contracts/contracts/src/interfaces/IPolicy.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/// @title IPolicy. -/// @notice Core policy interface for protected contract management. -interface IPolicy { - /// @notice Emitted when target contract is set. - event TargetSet(address indexed target); - - /// @notice Core error conditions. - error ZeroAddress(); - error UnsuccessfulCheck(); - error TargetNotSet(); - error TargetOnly(); - error TargetAlreadySet(); - error AlreadyEnforced(); - - /// @notice Returns policy trait identifier. - /// @return Policy trait string (e.g., "Semaphore"). - function trait() external pure returns (string memory); - - /// @notice Sets protected contract address. - /// @dev Owner-only, one-time setting. - /// @param _target Protected contract address. - function setTarget(address _target) external; -} diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index f84fb3f..ca23a53 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -1,37 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Test} from "forge-std/src/Test.sol"; +import {Test, Vm} from "forge-std/src/Test.sol"; import {NFT} from "./utils/NFT.sol"; import {BaseERC721Checker} from "./base/BaseERC721Checker.sol"; import {AdvancedERC721Checker} from "./advanced/AdvancedERC721Checker.sol"; +import {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; +import {AdvancedERC721CheckerFactory} from "./advanced/AdvancedERC721CheckerFactory.sol"; import {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol"; +import {AdvancedERC721PolicyFactory} from "./advanced/AdvancedERC721PolicyFactory.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 {IPolicy} from "../core/interfaces/IPolicy.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"; +import {IClone} from "../core/interfaces/IClone.sol"; +import {IAdvancedPolicy} from "../core/interfaces/IAdvancedPolicy.sol"; contract AdvancedChecker is Test { + event CloneDeployed(address indexed clone); + NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; AdvancedERC721Checker internal advancedChecker; - AdvancedERC721CheckerHarness internal advancedCheckerHarness; + BaseERC721CheckerFactory internal baseFactory; + AdvancedERC721CheckerFactory internal advancedFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal baseVerifiers; - address[] internal advancedVerifiers; bytes[] public evidence = new bytes[](1); - bytes[] public wrongEvidence = new bytes[](1); function setUp() public virtual { vm.startPrank(deployer); @@ -39,40 +40,40 @@ contract AdvancedChecker is Test { signupNft = new NFT(); rewardNft = new NFT(); - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); + baseChecker = BaseERC721Checker(baseClone); - advancedChecker = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - advancedCheckerHarness = new AdvancedERC721CheckerHarness(advancedVerifiers, 1, 0, 10); + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); + advancedChecker = AdvancedERC721Checker(advancedClone); evidence[0] = abi.encode(0); - wrongEvidence[0] = abi.encode(1); vm.stopPrank(); } - function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { - assertEq(advancedChecker.getVerifierAtIndex(0), address(signupNft)); + function test_factory_deployAndInitialize() public view { + assertEq(advancedChecker.initialized(), true); } - function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - advancedChecker.getVerifierAtIndex(5); + function test_checker_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + advancedChecker.initialize(); } - function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { - assertEq(advancedCheckerHarness.exposed__getVerifierAtIndex(0), address(signupNft)); - } - - function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - advancedCheckerHarness.exposed__getVerifierAtIndex(5); + function test_checker_getAppendedBytes() public { + assertEq( + advancedChecker.getAppendedBytes(), + abi.encode(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10) + ); } function test_checkPre_whenTokenDoesNotExist_reverts() public { @@ -143,144 +144,6 @@ contract AdvancedChecker is Test { vm.stopPrank(); } - - function test_checkerPre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_checkerPre_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.PRE)); - - vm.stopPrank(); - } - - function test_checkerPre_whenValid_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE)); - - vm.stopPrank(); - } - - function test_checkerMain_whenCallerHasNoTokens_returnsFalse() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.MAIN)); - - vm.stopPrank(); - } - - function test_checkerMain_whenCallerHasTokens_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.MAIN)); - - vm.stopPrank(); - } - - function test_checkerPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - vm.startPrank(target); - - rewardNft.mint(subject); - - assert(!advancedCheckerHarness.check(subject, evidence, Check.POST)); - - vm.stopPrank(); - } - - function test_checkerPost_whenValid_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.POST)); - - vm.stopPrank(); - } - - function test_internalPre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - advancedCheckerHarness.exposed__checkPre(subject, wrongEvidence); - - vm.stopPrank(); - } - - function test_internalPre_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(!advancedCheckerHarness.exposed__checkPre(notOwner, evidence)); - - vm.stopPrank(); - } - - function test_internalPre_whenValid_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__checkPre(subject, evidence)); - - vm.stopPrank(); - } - - function test_internalMain_whenCallerHasNoTokens_returnsFalse() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(!advancedCheckerHarness.exposed__checkMain(notOwner, evidence)); - - vm.stopPrank(); - } - - function test_internalMain_whenCallerHasTokens_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__checkMain(subject, evidence)); - - vm.stopPrank(); - } - - function test_internalPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - vm.startPrank(target); - - rewardNft.mint(subject); - - assert(!advancedCheckerHarness.exposed__checkPost(subject, evidence)); - - vm.stopPrank(); - } - - function test_internalPost_whenValid_succeeds() public { - vm.startPrank(target); - - signupNft.mint(subject); - - assert(advancedCheckerHarness.exposed__checkPost(subject, evidence)); - - vm.stopPrank(); - } } contract AdvancedPolicy is Test { @@ -290,20 +153,18 @@ contract AdvancedPolicy is Test { NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; + BaseERC721CheckerFactory internal baseFactory; AdvancedERC721Checker internal advancedChecker; - AdvancedERC721Checker internal advancedCheckerSkipped; + AdvancedERC721CheckerFactory internal advancedFactory; AdvancedERC721Policy internal policy; AdvancedERC721Policy internal policySkipped; - AdvancedERC721PolicyHarness internal policyHarness; - AdvancedERC721PolicyHarness internal policyHarnessSkipped; + AdvancedERC721PolicyFactory internal policyFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal baseVerifiers; - address[] internal advancedVerifiers; bytes[] public evidence = new bytes[](1); bytes[] public wrongEvidence = new bytes[](1); @@ -313,21 +174,34 @@ contract AdvancedPolicy is Test { signupNft = new NFT(); rewardNft = new NFT(); - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); + + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); + baseChecker = BaseERC721Checker(baseClone); - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); + advancedChecker = AdvancedERC721Checker(advancedClone); - advancedChecker = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - advancedCheckerSkipped = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - policy = new AdvancedERC721Policy(advancedChecker, false, false, true); - policyHarness = new AdvancedERC721PolicyHarness(advancedChecker, false, false, true); - policySkipped = new AdvancedERC721Policy(advancedCheckerSkipped, true, true, false); - policyHarnessSkipped = new AdvancedERC721PolicyHarness(advancedCheckerSkipped, true, true, false); + policyFactory = new AdvancedERC721PolicyFactory(); + + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), false, false, true); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); + policy = AdvancedERC721Policy(policyClone); + + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), true, true, false); + entries = vm.getRecordedLogs(); + address policyCloneSkipped = address(uint160(uint256(entries[0].topics[1]))); + policySkipped = AdvancedERC721Policy(policyCloneSkipped); evidence[0] = abi.encode(0); wrongEvidence[0] = abi.encode(1); @@ -335,11 +209,35 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_trait_returnsCorrectValue() public view { + function test_factory_deployAndInitialize() public view { + assertEq(policy.initialized(), true); + assertEq(policySkipped.initialized(), true); + } + + function test_policy_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policy.initialize(); + + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policySkipped.initialize(); + } + + function test_policy_getAppendedBytes() public { + assertEq( + policy.getAppendedBytes(), + abi.encode(address(deployer), address(advancedChecker), false, false, true) + ); + assertEq( + policySkipped.getAppendedBytes(), + abi.encode(address(deployer), address(advancedChecker), true, true, false) + ); + } + + function test_policy_trait_returnsCorrectValue() public view { assertEq(policy.trait(), "AdvancedERC721"); } - function test_setTarget_whenCallerNotOwner_reverts() public { + function test_policy_setTarget_whenCallerNotOwner_reverts() public { vm.startPrank(notOwner); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); @@ -348,7 +246,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenZeroAddress_reverts() public { + function test_policy_setTarget_whenZeroAddress_reverts() public { vm.startPrank(deployer); vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); @@ -357,7 +255,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenValid_succeeds() public { + function test_policy_setTarget_whenValid_succeeds() public { vm.startPrank(deployer); vm.expectEmit(true, true, true, true); @@ -368,7 +266,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenAlreadySet_reverts() public { + function test_policy_setTarget_whenAlreadySet_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -379,7 +277,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenCallerNotTarget_reverts() public { + function test_policy_enforcePre_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -394,7 +292,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenTokenDoesNotExist_reverts() public { + function test_policy_enforcePre_whenTokenDoesNotExist_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -409,7 +307,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenChecksSkipped_reverts() public { + function test_policy_enforcePre_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -425,7 +323,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenCheckFails_reverts() public { + function test_policy_enforcePre_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -441,7 +339,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenValid_succeeds() public { + function test_policy_enforcePre_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -459,7 +357,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenAlreadyEnforced_reverts() public { + function test_policy_enforcePre_whenAlreadyEnforced_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -477,7 +375,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenCallerNotTarget_reverts() public { + function test_policy_enforceMain_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -492,10 +390,23 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenCheckFails_reverts() public { + function test_policy_enforceMain_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); + signupNft.mint(subject); + + vm.stopPrank(); + + vm.startPrank(target); + + policy.enforce(subject, evidence, Check.PRE); + + vm.stopPrank(); + + vm.startPrank(subject); + + signupNft.transferFrom(subject, target, 0); vm.stopPrank(); @@ -507,7 +418,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenPreCheckMissing_reverts() public { + function test_policy_enforceMain_whenPreCheckMissing_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -523,7 +434,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenValid_succeeds() public { + function test_policy_enforceMain_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -543,7 +454,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenMultipleValid_succeeds() public { + function test_policy_enforceMain_whenMultipleValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -568,7 +479,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenMultipleNotAllowed_reverts() public { + function test_policy_enforceMain_whenMultipleNotAllowed_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -586,7 +497,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenPreCheckMissing_reverts() public { + function test_policy_enforcePost_whenPreCheckMissing_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -603,7 +514,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenCallerNotTarget_reverts() public { + function test_policy_enforcePost_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -618,7 +529,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenChecksSkipped_reverts() public { + function test_policy_enforcePost_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -636,7 +547,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenCheckFails_reverts() public { + function test_policy_enforcePost_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -657,7 +568,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenValid_succeeds() public { + function test_policy_enforcePost_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -678,7 +589,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenAlreadyEnforced_reverts() public { + function test_policy_enforcePost_whenAlreadyEnforced_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -697,366 +608,87 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } +} - function test_enforcePreInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(subject); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforcePreInternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforcePreInternal_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); - - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforcePreInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(notOwner, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforcePreInternal_whenValid_succeeds() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.PRE); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforcePreInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.stopPrank(); - } - - function test_enforceMainInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(subject); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - vm.stopPrank(); - } - - function test_enforceMainInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - vm.stopPrank(); - } - - function test_enforceMainInternal_whenPreCheckMissing_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - vm.stopPrank(); - } - - function test_enforceMainInternal_whenValid_succeeds() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); +contract Voting is Test { + event Registered(address voter); + event Voted(address voter, uint8 option); + event Eligible(address voter); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + NFT internal signupNft; + NFT internal rewardNft; + BaseERC721Checker internal baseChecker; + BaseERC721CheckerFactory internal baseFactory; + AdvancedERC721Checker internal advancedChecker; + AdvancedERC721CheckerFactory internal advancedFactory; + AdvancedERC721Policy internal policy; + AdvancedERC721PolicyFactory internal policyFactory; + AdvancedVoting internal voting; - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + address public deployer = vm.addr(0x1); + address public target = vm.addr(0x2); + address public subject = vm.addr(0x3); + address public notOwner = vm.addr(0x4); - vm.stopPrank(); - } + bytes[] public evidence = new bytes[](1); + bytes[] public wrongEvidence = new bytes[](1); - function test_enforceMainInternal_whenMultipleValid_succeeds() public { + function setUp() public virtual { vm.startPrank(deployer); - policyHarness.setTarget(target); - signupNft.mint(subject); + signupNft = new NFT(); + rewardNft = new NFT(); - vm.stopPrank(); + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); - vm.startPrank(target); + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); + baseChecker = BaseERC721Checker(baseClone); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); + advancedChecker = AdvancedERC721Checker(advancedClone); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + policyFactory = new AdvancedERC721PolicyFactory(); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), false, false, true); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); + policy = AdvancedERC721Policy(policyClone); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + evidence[0] = abi.encode(0); + wrongEvidence[0] = abi.encode(1); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + voting = new AdvancedVoting(policy); vm.stopPrank(); } - function test_enforceMainInternal_whenMultipleNotAllowed_reverts() public { - vm.startPrank(deployer); + function test_simple() public { + assertEq(address(voting.POLICY()), address(policy)); - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenPreCheckMissing_reverts() public { vm.startPrank(deployer); - policyHarness.setTarget(target); + policy.setTarget(address(voting)); signupNft.mint(subject); vm.stopPrank(); - vm.startPrank(target); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); - - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.POST); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - rewardNft.mint(subject); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenValid_succeeds() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.POST); - - policyHarness.exposed__enforce(subject, evidence, Check.POST); - - vm.stopPrank(); - } - - function test_enforcePostInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - signupNft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - policyHarness.exposed__enforce(subject, evidence, Check.POST); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); - + voting.register(0); vm.stopPrank(); } -} -contract Voting is Test { - event Registered(address voter); - event Voted(address voter, uint8 option); - event Eligible(address voter); - - NFT internal signupNft; - NFT internal rewardNft; - BaseERC721Checker internal baseChecker; - AdvancedERC721Checker internal advancedChecker; - AdvancedERC721Policy internal policy; - AdvancedVoting internal voting; - - address public deployer = vm.addr(0x1); - address public subject = vm.addr(0x2); - address public notOwner = vm.addr(0x3); - - address[] internal baseVerifiers; - address[] internal advancedVerifiers; - - function setUp() public virtual { - vm.startPrank(deployer); - - signupNft = new NFT(); - rewardNft = new NFT(); - - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); - - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); - - advancedChecker = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - policy = new AdvancedERC721Policy(advancedChecker, false, false, true); - voting = new AdvancedVoting(policy); - - vm.stopPrank(); + function test_voting_deployed() public view { + assertEq(address(voting.POLICY()), address(policy)); + assertEq(voting.voteCounts(0), 0); } function test_register_whenCallerNotTarget_reverts() public { diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index 60f9abc..4609ae9 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -1,103 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Test} from "forge-std/src/Test.sol"; +import {Test, Vm} from "forge-std/src/Test.sol"; import {NFT} from "./utils/NFT.sol"; import {BaseERC721Checker} from "./base/BaseERC721Checker.sol"; +import {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; +import {BaseERC721PolicyFactory} from "./base/BaseERC721PolicyFactory.sol"; 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 {IClone} from "../core/interfaces/IClone.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract BaseChecker is Test { NFT internal nft; BaseERC721Checker internal checker; - BaseERC721CheckerHarness internal checkerHarness; + BaseERC721CheckerFactory internal factory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - - address[] internal verifiers; bytes[] public evidence = new bytes[](1); - function setUp() public virtual { + function setUp() public { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); - checker = new BaseERC721Checker(verifiers); - checkerHarness = new BaseERC721CheckerHarness(verifiers); + factory = new BaseERC721CheckerFactory(); + + vm.recordLogs(); + factory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); + checker = BaseERC721Checker(baseClone); evidence[0] = abi.encode(0); vm.stopPrank(); } - function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { - assertEq(checker.getVerifierAtIndex(0), address(nft)); - } - - function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - checker.getVerifierAtIndex(1); + function test_factory_deployAndInitialize() public view { + assertEq(checker.initialized(), true); } - function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { - assertEq(checkerHarness.exposed__getVerifierAtIndex(0), address(nft)); + function test_checker_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + checker.initialize(); } - function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - checkerHarness.exposed__getVerifierAtIndex(1); + function test_checker_getAppendedBytes() public { + assertEq(checker.getAppendedBytes(), abi.encode(address(nft))); } function test_checker_whenTokenDoesNotExist_reverts() public { vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - checkerHarness.exposed__check(subject, evidence); - - vm.stopPrank(); - } - - function test_checker_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); - - nft.mint(subject); - - assert(!checkerHarness.exposed__check(notOwner, evidence)); - - vm.stopPrank(); - } - - function test_checker_whenCallerIsOwner_succeeds() public { - vm.startPrank(target); - - nft.mint(subject); - - assert(checkerHarness.exposed__check(subject, evidence)); - - vm.stopPrank(); - } - - function test_checkerExternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); checker.check(subject, evidence); vm.stopPrank(); } - function test_checkerExternal_whenCallerNotOwner_returnsFalse() public { + function test_checker_whenCallerNotOwner_returnsFalse() public { vm.startPrank(target); nft.mint(subject); @@ -107,7 +74,7 @@ contract BaseChecker is Test { vm.stopPrank(); } - function test_checkerExternal_whenCallerIsOwner_succeeds() public { + function test_checker_whenCallerIsOwner_succeeds() public { vm.startPrank(target); nft.mint(subject); @@ -124,48 +91,70 @@ contract BasePolicy is Test { NFT internal nft; BaseERC721Checker internal checker; + BaseERC721CheckerFactory internal checkerFactory; BaseERC721Policy internal policy; - BaseERC721PolicyHarness internal policyHarness; + BaseERC721PolicyFactory internal policyFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal verifiers; bytes[] public evidence = new bytes[](1); function setUp() public virtual { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); - checker = new BaseERC721Checker(verifiers); - policy = new BaseERC721Policy(checker); - policyHarness = new BaseERC721PolicyHarness(checker); + checkerFactory = new BaseERC721CheckerFactory(); + policyFactory = new BaseERC721PolicyFactory(); + + vm.recordLogs(); + checkerFactory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address checkerClone = address(uint160(uint256(entries[0].topics[1]))); + checker = BaseERC721Checker(checkerClone); + + vm.recordLogs(); + policyFactory.deploy(address(checker)); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); + policy = BaseERC721Policy(policyClone); evidence[0] = abi.encode(0); vm.stopPrank(); } - function test_trait_returnsCorrectValue() public view { + function test_factory_deployAndInitialize() public view { + assertEq(policy.initialized(), true); + } + + function test_policy_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policy.initialize(); + } + + function test_policy_getAppendedBytes() public { + assertEq(policy.getAppendedBytes(), abi.encode(address(deployer), address(checker))); + } + + function test_policy_trait_returnsCorrectValue() public view { assertEq(policy.trait(), "BaseERC721"); } - function test_getTarget_returnsExpectedAddress() public { + function test_policy_target_returnsExpectedAddress() public { vm.startPrank(deployer); policy.setTarget(target); - assertEq(policy.getTarget(), target); + assertEq(policy.target(), target); vm.stopPrank(); } - function test_setTarget_whenCallerNotOwner_reverts() public { + function test_policy_setTarget_whenCallerNotOwner_reverts() public { vm.startPrank(notOwner); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); @@ -174,7 +163,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_setTarget_whenZeroAddress_reverts() public { + function test_policy_setTarget_whenZeroAddress_reverts() public { vm.startPrank(deployer); vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); @@ -183,7 +172,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_setTarget_whenValidAddress_succeeds() public { + function test_policy_setTarget_whenValidAddress_succeeds() public { vm.startPrank(deployer); vm.expectEmit(true, true, true, true); @@ -194,7 +183,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_setTarget_whenAlreadySet_reverts() public { + function test_policy_setTarget_whenAlreadySet_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -205,7 +194,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_enforce_whenCallerNotTarget_reverts() public { + function test_policy_enforce_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -220,7 +209,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_enforce_whenTokenDoesNotExist_reverts() public { + function test_policy_enforce_whenTokenDoesNotExist_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -235,7 +224,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_enforce_whenCheckFails_reverts() public { + function test_policy_enforce_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -251,7 +240,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_enforce_whenValid_succeeds() public { + function test_policy_enforce_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -269,7 +258,7 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_enforce_whenAlreadyEnforced_reverts() public { + function test_policy_enforce_whenAlreadyEnforced_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -286,88 +275,6 @@ contract BasePolicy is Test { vm.stopPrank(); } - - function test_enforceInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(subject); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence); - - vm.stopPrank(); - } - - function test_enforceInternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policyHarness.exposed__enforce(subject, evidence); - - vm.stopPrank(); - } - - function test_enforceInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(notOwner, evidence); - - vm.stopPrank(); - } - - function test_enforceInternal_whenValid_succeeds() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence); - - policyHarness.exposed__enforce(subject, evidence); - - vm.stopPrank(); - } - - function test_enforceInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - - policyHarness.exposed__enforce(subject, evidence); - - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence); - - vm.stopPrank(); - } } contract Voting is Test { @@ -376,29 +283,46 @@ contract Voting is Test { NFT internal nft; BaseERC721Checker internal checker; + BaseERC721CheckerFactory internal checkerFactory; BaseERC721Policy internal policy; + BaseERC721PolicyFactory internal policyFactory; BaseVoting internal voting; address public deployer = vm.addr(0x1); address public subject = vm.addr(0x2); address public notOwner = vm.addr(0x3); - address[] internal verifiers; - function setUp() public virtual { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); - checker = new BaseERC721Checker(verifiers); - policy = new BaseERC721Policy(checker); + checkerFactory = new BaseERC721CheckerFactory(); + policyFactory = new BaseERC721PolicyFactory(); + + vm.recordLogs(); + checkerFactory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address checkerClone = address(uint160(uint256(entries[0].topics[1]))); + checker = BaseERC721Checker(checkerClone); + + vm.recordLogs(); + policyFactory.deploy(address(checker)); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); + policy = BaseERC721Policy(policyClone); + voting = new BaseVoting(policy); vm.stopPrank(); } + function test_voting_deployed() public view { + assertEq(address(voting.POLICY()), address(policy)); + assertEq(voting.hasVoted(subject), false); + assertEq(voting.voteCounts(0), 0); + } + function test_register_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); @@ -532,6 +456,9 @@ contract Voting is Test { voting.vote(0); + assertEq(voting.hasVoted(subject), true); + assertEq(voting.voteCounts(0), 1); + vm.stopPrank(); } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index 644b944..18461ec 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -1,79 +1,77 @@ // 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"; -/// @title AdvancedERC721Checker. -/// @notice Multi-phase NFT validation with aggregated verification contracts. -/// @dev Implements three-phase validation using multiple NFT contracts and external verifiers: -/// - Pre-check: Basic signup token validation using BaseERC721Checker. -/// - Main-check: Balance threshold validation for signup token. -/// - Post-check: Reward eligibility verification for reward token. +/// @title AdvancedERC721Checker +/// @notice Multi-phase NFT validation using external contracts and thresholds. +/// @dev Implements three-phase validation: +/// - Pre-check: Basic ownership verification using `BaseERC721Checker`. +/// - Main-check: Ensures a minimum token balance. +/// - Post-check: Validates reward eligibility. contract AdvancedERC721Checker is AdvancedChecker { - /// @notice External contracts used for verification. - /// @dev Immutable references derived from verifier array positions: - /// - Index 0: Signup NFT contract. - /// - Index 1: Reward NFT contract. - /// - Index 2: Base ERC721 checker contract. - IERC721 public immutable SIGNUP_NFT; - IERC721 public immutable REWARD_NFT; - BaseERC721Checker public immutable BASE_ERC721_CHECKER; + /// @notice External verification contracts and thresholds. + IERC721 public signupNft; + IERC721 public rewardNft; + BaseERC721Checker public baseERC721Checker; - /// @notice Validation thresholds. - uint256 public immutable MIN_BALANCE; - uint256 public immutable MIN_TOKEN_ID; - uint256 public immutable MAX_TOKEN_ID; + uint256 public minBalance; + uint256 public minTokenId; + uint256 public maxTokenId; - /// @notice Initializes checker with verification chain. - /// @dev Orders of verifiers array is crucial: - /// [signupNFT, rewardNFT, baseChecker] - /// @param _verifiers Ordered array of verification contract addresses. - /// @param _minBalance Required signup token balance. - /// @param _minTokenId Lower bound for valid token IDs. - /// @param _maxTokenId Upper bound for valid token IDs. - constructor( - address[] memory _verifiers, - uint256 _minBalance, - uint256 _minTokenId, - uint256 _maxTokenId - ) AdvancedChecker(_verifiers) { - SIGNUP_NFT = IERC721(_getVerifierAtIndex(0)); - REWARD_NFT = IERC721(_getVerifierAtIndex(1)); - BASE_ERC721_CHECKER = BaseERC721Checker(_getVerifierAtIndex(2)); - MIN_BALANCE = _minBalance; - MIN_TOKEN_ID = _minTokenId; - MAX_TOKEN_ID = _maxTokenId; + /// @notice Initializes the checker with external contract references and thresholds. + /// @dev Decodes appended bytes to set state variables. + function _initialize() internal override { + super._initialize(); + + bytes memory data = _getAppendedBytes(); + + ( + address signupNftAddr, + address rewardNftAddr, + address baseCheckerAddr, + uint256 minBalance_, + uint256 minTokenId_, + uint256 maxTokenId_ + ) = abi.decode(data, (address, address, address, uint256, uint256, uint256)); + + signupNft = IERC721(signupNftAddr); + rewardNft = IERC721(rewardNftAddr); + baseERC721Checker = BaseERC721Checker(baseCheckerAddr); + minBalance = minBalance_; + minTokenId = minTokenId_; + maxTokenId = maxTokenId_; } - /// @notice Pre-check: Validates initial NFT ownership. - /// @dev Delegates basic ownership check to BaseERC721Checker. + /// @notice Pre-check: Validates ownership using the base checker. /// @param subject Address to validate. - /// @param evidence Array containing encoded tokenId. - /// @return Validation status from base checker. + /// @param evidence Encoded tokenId. + /// @return Boolean indicating validation success. function _checkPre(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPre(subject, evidence); - return BASE_ERC721_CHECKER.check(subject, evidence); + + return baseERC721Checker.check(subject, evidence); } - /// @notice Main-check: Validates token balance requirements. - /// @dev Ensures subject has exactly MIN_BALANCE tokens. + /// @notice Main-check: Ensures token balance meets requirements. /// @param subject Address to validate. - /// @param evidence Not used in balance check. - /// @return True if balance meets requirements. + /// @param evidence Not used in this validation. + /// @return Boolean indicating validation success. function _checkMain(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkMain(subject, evidence); - return SIGNUP_NFT.balanceOf(subject) >= MIN_BALANCE && SIGNUP_NFT.balanceOf(subject) <= MIN_BALANCE; + + return signupNft.balanceOf(subject) >= minBalance; } /// @notice Post-check: Validates reward eligibility. - /// @dev Ensures subject doesn't already have reward tokens. /// @param subject Address to validate. - /// @param evidence Not used in reward check. - /// @return True if subject eligible for rewards. + /// @param evidence Not used in this validation. + /// @return Boolean indicating validation success. function _checkPost(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPost(subject, evidence); - return REWARD_NFT.balanceOf(subject) == 0; + + return rewardNft.balanceOf(subject) == 0; } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol new file mode 100644 index 0000000..89c68e8 --- /dev/null +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; + +/// @title AdvancedERC721CheckerFactory +/// @notice Factory for deploying minimal proxy instances of AdvancedERC721Checker. +/// @dev Encodes configuration data for each clone. +contract AdvancedERC721CheckerFactory is Factory { + /// @notice Initializes the factory with the AdvancedERC721Checker implementation. + constructor() Factory(address(new AdvancedERC721Checker())) {} + + /// @notice Deploys a new AdvancedERC721Checker clone. + /// @dev Encodes and appends configuration data for the clone. + /// @param _nftAddress Address of the signup NFT contract. + /// @param _rewardNft Address of the reward NFT contract. + /// @param _baseERC721Checker Address of the base checker contract. + /// @param _minBalance Minimum balance required for validation. + /// @param _minTokenId Minimum token ID for validation. + /// @param _maxTokenId Maximum token ID for validation. + function deploy( + address _nftAddress, + address _rewardNft, + address _baseERC721Checker, + uint256 _minBalance, + uint256 _minTokenId, + uint256 _maxTokenId + ) public { + bytes memory data = abi.encode( + _nftAddress, + _rewardNft, + _baseERC721Checker, + _minBalance, + _minTokenId, + _maxTokenId + ); + + address clone = super._deploy(data); + + AdvancedERC721Checker(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol index b852777..84bb645 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol @@ -1,22 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedPolicy} from "../../AdvancedPolicy.sol"; -import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; +import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; -/// @title AdvancedERC721Policy. -/// @notice Three-phase ERC721 validation policy. -/// @dev Enforces multi-stage checks through AdvancedERC721Checker. +/// @title AdvancedERC721Policy +/// @notice Three-phase policy contract for ERC721 validation. +/// @dev Leverages AdvancedChecker for pre, main, and post validation phases. contract AdvancedERC721Policy is AdvancedPolicy { - /// @notice Initializes with checker contract. - constructor( - AdvancedERC721Checker _checker, - bool _skipPre, - bool _skipPost, - bool _allowMultipleMain - ) AdvancedPolicy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} - - /// @notice Returns policy identifier. + /// @notice Returns a unique identifier for the policy. + /// @return The string identifier "AdvancedERC721". function trait() external pure returns (string memory) { return "AdvancedERC721"; } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol new file mode 100644 index 0000000..fd53c32 --- /dev/null +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AdvancedERC721Policy} from "./AdvancedERC721Policy.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; + +/// @title AdvancedERC721PolicyFactory +/// @notice Factory for deploying minimal proxy instances of AdvancedERC721Policy. +/// @dev Encodes configuration data for multi-phase policy validation. +contract AdvancedERC721PolicyFactory is Factory { + /// @notice Initializes the factory with the AdvancedERC721Policy implementation. + constructor() Factory(address(new AdvancedERC721Policy())) {} + + /// @notice Deploys a new AdvancedERC721Policy clone. + /// @param _checkerAddr Address of the associated checker contract. + /// @param _skipPre Whether to skip pre-checks. + /// @param _skipPost Whether to skip post-checks. + /// @param _allowMultipleMain Whether multiple main checks are allowed. + function deploy(address _checkerAddr, bool _skipPre, bool _skipPost, bool _allowMultipleMain) public { + bytes memory data = abi.encode(msg.sender, _checkerAddr, _skipPre, _skipPost, _allowMultipleMain); + + address clone = super._deploy(data); + + AdvancedERC721Policy(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol index 8d69c78..b46eb1f 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol @@ -1,105 +1,116 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedPolicy} from "../../AdvancedPolicy.sol"; -import {Check} from "../../interfaces/IAdvancedPolicy.sol"; - -/// @title AdvancedVoting. -/// @notice Advanced voting system with NFT-based phases and eligibility verification. -/// @dev Implements a three-phase governance process using NFT validation: -/// 1. Registration: Validates ownership of signup NFT (under-the-hood uses the BaseERC721Checker). -/// 2. Voting: Validates token balances and records votes (single token = single vote). -/// 3. Eligibility: Validates criteria for governance participation benefits. +import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; +import {Check} from "../../core/interfaces/IAdvancedPolicy.sol"; + +/// @title AdvancedVoting +/// @notice Multi-phase governance system with NFT-based validation. +/// @dev Combines pre, main, and post phases for registration, voting, and eligibility verification. contract AdvancedVoting { - /// @notice Emitted on successful phase completion. - /// @param voter Address that completed the phase. + /// @notice Emitted when a voter registers successfully. + /// @param voter Address of the voter who registered. event Registered(address voter); - /// @param option Selected voting option (0 or 1). + + /// @notice Emitted when a vote is cast successfully. + /// @param voter Address of the voter who cast their vote. + /// @param option The chosen voting option (0 or 1). event Voted(address voter, uint8 option); - /// @param voter Address that met eligibility criteria. + + /// @notice Emitted when a voter is deemed eligible. + /// @param voter Address of the voter who met eligibility criteria. event Eligible(address voter); - /// @notice Validation error states. - /// @dev Thrown when phase requirements not met. - error NotRegistered(); // Pre-check (registration) not completed. - error NotVoted(); // Main check (voting) not completed. - error AlreadyEligible(); // Post check (eligibility) already verified. - error InvalidOption(); // Vote option out of valid range. - error NotEligible(); // Eligibility criteria not met. + /// @notice Error thrown when a user attempts an action without registering first. + error NotRegistered(); + + /// @notice Error thrown when a user attempts to verify eligibility without voting. + error NotVoted(); + + /// @notice Error thrown when a user tries to verify eligibility more than once. + error AlreadyEligible(); + + /// @notice Error thrown when an invalid voting option is provided. + error InvalidOption(); - /// @notice Policy contract managing multi-phase validation. - /// @dev Handles all NFT-based checks through aggregated verifiers. + /// @notice Error thrown when a user does not meet the eligibility criteria. + error NotEligible(); + + /// @notice Reference to the policy contract enforcing multi-phase validation. AdvancedPolicy public immutable POLICY; - /// @notice Vote tracking per option. - /// @dev Maps option ID (0 or 1) to total votes received. + /// @notice Tracks the vote count for each option (0 or 1). mapping(uint8 => uint256) public voteCounts; - /// @notice Initializes voting system. - /// @param _policy Advanced policy contract with configured verifiers. + /// @notice Constructor to set the policy contract. + /// @param _policy Address of the AdvancedPolicy contract to use for validation. constructor(AdvancedPolicy _policy) { POLICY = _policy; } - /// @notice Registration phase handler. - /// @dev Validates signup NFT ownership using BaseERC721Checker. - /// @param tokenId ID of the signup NFT to validate. - /// @custom:requirements - /// - Token must exist. - /// - Caller must be token owner. - /// - Token ID must be within valid range. - /// @custom:emits Registered when registration succeeds. + /// @notice Registers a user for voting by validating their NFT ownership. + /// @dev Enforces the pre-check phase using the AdvancedPolicy contract. + /// @param tokenId The ID of the NFT used to verify registration eligibility. function register(uint256 tokenId) external { + // Prepare evidence with the tokenId encoded as bytes. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(tokenId); + // Enforce the pre-check phase using the provided policy. POLICY.enforce(msg.sender, _evidence, Check.PRE); + // Emit an event to log the registration. emit Registered(msg.sender); } - /// @notice Voting phase handler. - /// @dev Validates voting power and records vote choice. - /// @param option Binary choice (0 or 1). - /// @custom:requirements - /// - Registration must be completed. - /// - Option must be valid (0 or 1). - /// - Token balance must meet requirements. - /// @custom:emits Voted when vote is recorded. + /// @notice Allows a registered user to cast their vote. + /// @dev Enforces the main-check phase and updates the vote count. + /// @param option The chosen voting option (0 or 1). function vote(uint8 option) external { - (bool pre, , ) = POLICY.enforced(address(this), msg.sender); + // Retrieve the enforcement status of the sender from the policy. + (bool pre, , ) = POLICY.enforced(msg.sender); + + // Ensure the user has registered before voting. if (!pre) revert NotRegistered(); + + // Validate that the voting option is within the allowed range. if (option >= 2) revert InvalidOption(); + // Prepare evidence with the chosen option encoded as bytes. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(option); + // Enforce the main-check phase using the policy. POLICY.enforce(msg.sender, _evidence, Check.MAIN); + // Increment the vote count for the chosen option. unchecked { voteCounts[option]++; } + // Emit an event to log the voting action. emit Voted(msg.sender, option); } - /// @notice Eligibility verification phase. - /// @dev Validates completion of governance process and checks eligibility criteria. - /// @custom:requirements - /// - Caller must be registered (passed PRE check). - /// - Caller must have voted (passed MAIN check). - /// - Caller must not be already verified (no POST check). - /// - Caller must meet eligibility criteria (no existing benefits). - /// @custom:emits Eligible when verification succeeds. + /// @notice Verifies a user's eligibility after voting has concluded. + /// @dev Enforces the post-check phase to ensure eligibility criteria are met. function eligible() external { - (bool pre, uint8 main, bool post) = POLICY.enforced(address(this), msg.sender); + // Retrieve the enforcement status for all phases. + (bool pre, uint8 main, bool post) = POLICY.enforced(msg.sender); + // Ensure the user has completed the registration phase. if (!pre) revert NotRegistered(); + + // Ensure the user has cast at least one vote. if (main == 0) revert NotVoted(); + + // Ensure the user has not already been marked as eligible. if (post) revert AlreadyEligible(); + // Enforce the post-check phase using the policy. POLICY.enforce(msg.sender, new bytes[](1), Check.POST); + // Emit an event to log the eligibility status. emit Eligible(msg.sender); } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol index 759fc02..dc1216f 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -1,29 +1,38 @@ // 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. +/// @title BaseERC721Checker /// @notice ERC721 token ownership validator. -/// @dev Extends BaseChecker for NFT ownership verification. +/// @dev Extends BaseChecker to implement NFT ownership validation logic. contract BaseERC721Checker is BaseChecker { - /// @notice NFT contract reference. - IERC721 public immutable NFT; + /// @notice Address of the ERC721 contract used for ownership validation. + IERC721 public nft; - /// @notice Initializes with ERC721 contract. - /// @param _verifiers Array of addresses for existing verification contracts. - constructor(address[] memory _verifiers) BaseChecker(_verifiers) { - NFT = IERC721(_getVerifierAtIndex(0)); + /// @notice Initializes the contract with an ERC721 contract address. + /// @dev Decodes the appended bytes from the clone to set the `nft` address. + function _initialize() internal override { + super._initialize(); + + bytes memory data = _getAppendedBytes(); + + address nftAddress = abi.decode(data, (address)); + + nft = IERC721(nftAddress); } - /// @notice Validates token ownership. - /// @param subject Address to check. - /// @param evidence Encoded tokenId. - /// @return True if subject owns token. + /// @notice Validates whether the subject owns a specific NFT. + /// @dev Decodes the token ID from evidence and checks ownership via the ERC721 contract. + /// @param subject Address to validate ownership for. + /// @param evidence Encoded token ID used for validation. + /// @return Boolean indicating whether the subject owns the token. function _check(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._check(subject, evidence); + uint256 tokenId = abi.decode(evidence[0], (uint256)); - return NFT.ownerOf(tokenId) == subject; + + return nft.ownerOf(tokenId) == subject; } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol new file mode 100644 index 0000000..2b50829 --- /dev/null +++ b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseERC721Checker} from "./BaseERC721Checker.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; + +/// @title BaseERC721CheckerFactory +/// @notice Factory contract for deploying minimal proxy instances of BaseERC721Checker. +/// @dev Simplifies deployment of ERC721 checker clones with appended configuration data. +contract BaseERC721CheckerFactory is Factory { + /// @notice Initializes the factory with the BaseERC721Checker implementation. + constructor() Factory(address(new BaseERC721Checker())) {} + + /// @notice Deploys a new BaseERC721Checker clone with the specified NFT contract address. + /// @dev Encodes the NFT contract address as configuration data for the clone. + /// @param _nftAddress Address of the ERC721 contract to validate ownership. + function deploy(address _nftAddress) public { + // Encode the NFT address for the appended data. + bytes memory data = abi.encode(_nftAddress); + + // Deploy the clone and initialize it with the encoded data. + address clone = super._deploy(data); + + BaseERC721Checker(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol index 2b112ce..591c598 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol @@ -1,24 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BasePolicy} from "../../../src/BasePolicy.sol"; -import {BaseERC721Checker} from "./BaseERC721Checker.sol"; +import {BasePolicy} from "../../core/policy/BasePolicy.sol"; -/// @title BaseERC721Policy. -/// @notice Policy enforcer for ERC721 token validation. -/// @dev Extends BasePolicy with NFT-specific checks. +/// @title BaseERC721Policy +/// @notice Policy contract enforcing NFT-based validation. +/// @dev Extends BasePolicy to add specific behavior for ERC721 token validation. contract BaseERC721Policy is BasePolicy { - /// @notice Checker contract reference. - BaseERC721Checker public immutable CHECKER; - - /// @notice Initializes with checker contract. - /// @param _checker Checker contract address. - constructor(BaseERC721Checker _checker) BasePolicy(_checker) { - CHECKER = BaseERC721Checker(_checker); - } - - /// @notice Returns policy identifier. - /// @return Policy trait string. + /// @notice Returns a trait identifier for the policy. + /// @dev Used to identify the policy type. + /// @return The trait string "BaseERC721". function trait() external pure returns (string memory) { return "BaseERC721"; } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol new file mode 100644 index 0000000..916bd49 --- /dev/null +++ b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseERC721Policy} from "./BaseERC721Policy.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; + +/// @title BaseERC721PolicyFactory +/// @notice Factory contract for deploying minimal proxy instances of BaseERC721Policy. +/// @dev Simplifies deployment of ERC721 policy clones with appended configuration data. +contract BaseERC721PolicyFactory is Factory { + /// @notice Initializes the factory with the BaseERC721Policy implementation. + constructor() Factory(address(new BaseERC721Policy())) {} + + /// @notice Deploys a new BaseERC721Policy clone with the specified checker address. + /// @dev Encodes the checker address and caller as configuration data for the clone. + /// @param _checkerAddr Address of the ERC721 checker to use for validation. + function deploy(address _checkerAddr) public { + // Encode the caller (owner) and checker address for appended data. + bytes memory data = abi.encode(msg.sender, _checkerAddr); + + address clone = super._deploy(data); + + BaseERC721Policy(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/base/BaseVoting.sol b/packages/contracts/contracts/src/test/base/BaseVoting.sol index 4f37794..a6d76b7 100644 --- a/packages/contracts/contracts/src/test/base/BaseVoting.sol +++ b/packages/contracts/contracts/src/test/base/BaseVoting.sol @@ -3,66 +3,67 @@ pragma solidity ^0.8.20; import {BaseERC721Policy} from "./BaseERC721Policy.sol"; -/// @title BaseVoting. -/// @notice Simple voting system with NFT-based access control. -/// @dev Implements basic voting functionality with two phases: -/// 1. Registration: Validates NFT ownership. -/// 2. Voting: Records validated votes. +/// @title BaseVoting +/// @notice Simple NFT-based voting system. +/// @dev Implements a basic two-phase voting system (registration and voting) with access control enforced by NFTs. contract BaseVoting { - /// @notice Emitted on successful registration/voting. + /// @notice Emitted when a voter successfully registers. + /// @param voter Address of the registered voter. event Registered(address voter); + + /// @notice Emitted when a voter successfully casts a vote. + /// @param voter Address of the voter. + /// @param option The option the voter chose. event Voted(address voter, uint8 option); - /// @notice System error conditions. + /// @notice Error thrown when a user attempts to vote without registering. error NotRegistered(); + + /// @notice Error thrown when a user attempts to vote more than once. error AlreadyVoted(); + + /// @notice Error thrown when a user attempts to vote with an invalid option. error InvalidOption(); - /// @notice Policy contract for NFT validation. + /// @notice Policy contract enforcing NFT-based registration. BaseERC721Policy public immutable POLICY; - /// @dev Maps voter address => voting status. + /// @dev Tracks whether an address has voted. mapping(address => bool) public hasVoted; - /// @dev Maps option ID => vote count. + + /// @dev Tracks the number of votes for each option. mapping(uint8 => uint256) public voteCounts; - /// @notice Sets up voting system. - /// @param _policy Contract for voter validation. + /// @notice Initializes the voting system with a specific policy contract. + /// @param _policy Address of the policy contract enforcing access control. constructor(BaseERC721Policy _policy) { POLICY = _policy; } - /// @notice Register using NFT ownership proof. - /// @dev Enforces NFT ownership check through policy. - /// @param tokenId NFT used for registration. - /// @custom:requirements Caller must own the NFT with tokenId. - /// @custom:emits Registered on successful registration. + /// @notice Registers a voter based on NFT ownership. + /// @dev Enforces ownership validation via the policy contract. + /// @param tokenId Token ID of the NFT used for validation. function register(uint256 tokenId) external { - // Encode token ID for policy verification. + // Encode the token ID for policy validation. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(tokenId); - // Verify NFT ownership. + // Enforce NFT ownership validation. POLICY.enforce(msg.sender, _evidence); emit Registered(msg.sender); } - /// @notice Cast vote after registration. - /// @dev Updates vote counts if validation passes. - /// @param option Vote choice (0 or 1). - /// @custom:requirements - /// - Caller must be registered. - /// - Caller must not have voted. - /// - Option must be valid (0 or 1). - /// @custom:emits Voted on successful vote cast. + /// @notice Casts a vote after successful registration. + /// @dev Validates voter registration and option validity before recording the vote. + /// @param option The chosen voting option (0 or 1). function vote(uint8 option) external { - // Verify registration and voting status. - if (!POLICY.enforced(address(this), msg.sender)) revert NotRegistered(); + // Check registration and voting status. + if (!POLICY.enforced(msg.sender)) revert NotRegistered(); if (hasVoted[msg.sender]) revert AlreadyVoted(); if (option >= 2) revert InvalidOption(); - // Record vote. + // Record the vote. hasVoted[msg.sender] = true; voteCounts[option]++; diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol deleted file mode 100644 index a48995e..0000000 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; -import {Check} from "../../interfaces/IAdvancedChecker.sol"; - -/// @title AdvancedERC721CheckerHarness. -/// @notice Test harness exposing internal methods of AdvancedERC721Checker. -/// @dev Inherits AdvancedERC721Checker and exposes protected methods for testing. -contract AdvancedERC721CheckerHarness is AdvancedERC721Checker { - /// @notice Initializes test harness with checker configuration. - /// @param _verifiers Array of addresses for existing verification contracts. - /// @param _minBalance Minimum token balance required. - /// @param _minTokenId Minimum valid token ID. - /// @param _maxTokenId Maximum valid token ID. - constructor( - address[] memory _verifiers, - uint256 _minBalance, - uint256 _minTokenId, - uint256 _maxTokenId - ) AdvancedERC721Checker(_verifiers, _minBalance, _minTokenId, _maxTokenId) {} - - /// @notice Test exposure for _check method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check to perform. - /// @return Validation result. - function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { - return _check(subject, evidence, checkType); - } - - /// @notice Test exposure for _checkPre method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Pre-check validation result. - function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPre(subject, evidence); - } - - /// @notice Test exposure for _checkMain method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Main validation result. - function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkMain(subject, evidence); - } - - /// @notice Test exposure for _checkPost method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Post-check validation result. - function exposed__checkPost(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPost(subject, evidence); - } - - /// @notice Test exposure for _getVerifierAtIndex method. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { - return _getVerifierAtIndex(index); - } -} diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol deleted file mode 100644 index 46a1518..0000000 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol"; -import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; -import {Check} from "../../interfaces/IAdvancedChecker.sol"; - -/// @title AdvancedERC721PolicyHarness. -/// @notice Test harness for AdvancedERC721Policy internal methods. -contract AdvancedERC721PolicyHarness is AdvancedERC721Policy { - /// @notice Initializes test harness. - constructor( - AdvancedERC721Checker _checker, - bool _skipPre, - bool _skipPost, - bool _allowMultipleMain - ) AdvancedERC721Policy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} - - /// @notice Test exposure for _enforce method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check type to enforce. - function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { - _enforce(subject, evidence, checkType); - } -} diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol deleted file mode 100644 index f5458bb..0000000 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; - -/// @title BaseERC721CheckerHarness. -/// @notice Test harness for BaseERC721Checker internal methods. -contract BaseERC721CheckerHarness is BaseERC721Checker { - /// @notice Initializes test harness with NFT contract. - /// @param _verifiers Array of addresses for existing verification contracts. - constructor(address[] memory _verifiers) BaseERC721Checker(_verifiers) {} - - /// @notice Test exposure for _check method. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Validation result. - function exposed__check(address subject, bytes[] calldata evidence) public view returns (bool) { - return _check(subject, evidence); - } - - /// @notice Test exposure for _getVerifierAtIndex method. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { - return _getVerifierAtIndex(index); - } -} diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol deleted file mode 100644 index 26c047b..0000000 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721Policy} from "../base/BaseERC721Policy.sol"; -import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; - -/// @title BaseERC721PolicyHarness. -/// @notice Test harness for BaseERC721Policy internal methods. -contract BaseERC721PolicyHarness is BaseERC721Policy { - /// @notice Initializes test harness with checker. - constructor(BaseERC721Checker _checker) BaseERC721Policy(_checker) {} - - /// @notice Test exposure for _enforce method. - /// @param subject Address to validate. - /// @param evidence Validation data. - function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { - _enforce(subject, evidence); - } -} diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 327308e..83d29ac 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -49,6 +49,7 @@ "typescript": "5.3" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.2" + "@openzeppelin/contracts": "^5.0.2", + "solady": "^0.0.298" } } diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt index b0866a6..0ca80f4 100644 --- a/packages/contracts/remappings.txt +++ b/packages/contracts/remappings.txt @@ -1,2 +1,3 @@ @openzeppelin/=node_modules/@openzeppelin/ -forge-std/=node_modules/forge-std/ +@solady/=node_modules/solady/ +forge-std/=node_modules/forge-std/ \ No newline at end of file diff --git a/packages/contracts/test/Advanced.test.ts b/packages/contracts/test/Advanced.test.ts index ce76a40..1e9150e 100644 --- a/packages/contracts/test/Advanced.test.ts +++ b/packages/contracts/test/Advanced.test.ts @@ -1,25 +1,28 @@ -import { expect } from "chai" -import { ethers } from "hardhat" import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" +import { ethers } from "hardhat" +import { expect } from "chai" import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" import { - BaseERC721Checker, + NFT__factory, BaseERC721Checker__factory, - AdvancedERC721Checker, + BaseERC721CheckerFactory__factory, + AdvancedERC721CheckerFactory__factory, + NFT, + BaseERC721Checker, + BaseERC721CheckerFactory, + AdvancedERC721CheckerFactory, + IERC721Errors, AdvancedERC721Checker__factory, - AdvancedERC721CheckerHarness, - AdvancedERC721CheckerHarness__factory, + AdvancedERC721Checker, + AdvancedERC721PolicyFactory__factory, + AdvancedERC721PolicyFactory, AdvancedERC721Policy, AdvancedERC721Policy__factory, - AdvancedERC721PolicyHarness, - AdvancedERC721PolicyHarness__factory, - AdvancedVoting, AdvancedVoting__factory, - IERC721Errors, - NFT, - NFT__factory + AdvancedVoting } from "../typechain-types" +/* eslint-disable @typescript-eslint/no-shadow */ describe("Advanced", () => { describe("Checker", () => { async function deployAdvancedCheckerFixture() { @@ -27,309 +30,193 @@ describe("Advanced", () => { const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721CheckerHarnessFactory: AdvancedERC721CheckerHarness__factory = - await ethers.getContractFactory("AdvancedERC721CheckerHarness") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), 1, 0, 10 ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } - const advancedCheckerHarness: AdvancedERC721CheckerHarness = - await AdvancedERC721CheckerHarnessFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], - 1, - 0, - 10 - ) + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) // mint 0 for subject. await signupNft.connect(deployer).mint(subjectAddress) // encoded token ids. - const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { signupNft, rewardNft, baseChecker, advancedChecker, - advancedCheckerHarness, deployer, target, subject, subjectAddress, notOwnerAddress, - validNFTId, - invalidNFTId + validEncodedNFTId, + invalidEncodedNFTId } } - describe("constructor", () => { - it("deploys correctly", async () => { + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) expect(advancedChecker).to.not.eq(undefined) + expect(await advancedChecker.initialized()).to.be.eq(true) }) - }) - - describe("check", () => { - describe("pre check", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await expect( - advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") - }) - - it("returns false when not owner", async () => { - const { advancedChecker, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal( - false - ) - }) - - it("succeeds when valid", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal( - true - ) - }) - }) - describe("main check", () => { - it("returns false when balance insufficient", async () => { - const { advancedChecker, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal( - false - ) - }) - it("succeeds when balance sufficient", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal( - true - ) - }) - }) - describe("post check", () => { - it("reverts when already rewarded", async () => { - const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) + it("should revert when already initialized", async () => { + const { advancedChecker, deployer } = await loadFixture(deployAdvancedCheckerFixture) - await rewardNft.mint(subjectAddress) - - expect(await advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 2)).to.be.equal( - false - ) - }) - - it("succeeds when in valid range", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 2)).to.be.equal( - true - ) - }) - }) - }) - - describe("getVerifierAtIndex", () => { - it("returns correct verifier address", async () => { - const { advancedChecker, signupNft } = await loadFixture(deployAdvancedCheckerFixture) - expect(await advancedChecker.getVerifierAtIndex(0)).to.equal(await signupNft.getAddress()) - }) - - it("reverts when index out of bounds", async () => { - const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) - await expect(advancedChecker.getVerifierAtIndex(5)).to.be.revertedWithCustomError( + await expect(advancedChecker.connect(deployer).initialize()).to.be.revertedWithCustomError( advancedChecker, - "VerifierNotFound" + "AlreadyInitialized" ) }) }) - describe("internal getVerifierAtIndex", () => { - it("returns correct verifier address", async () => { - const { advancedCheckerHarness, signupNft } = await loadFixture(deployAdvancedCheckerFixture) - expect(await advancedCheckerHarness.exposed__getVerifierAtIndex(0)).to.equal( - await signupNft.getAddress() - ) - }) + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { advancedChecker, signupNft, rewardNft, baseChecker } = + await loadFixture(deployAdvancedCheckerFixture) - it("reverts when index out of bounds", async () => { - const { advancedCheckerHarness } = await loadFixture(deployAdvancedCheckerFixture) - await expect(advancedCheckerHarness.exposed__getVerifierAtIndex(5)).to.be.revertedWithCustomError( - advancedCheckerHarness, - "VerifierNotFound" - ) + const appendedBytes = await advancedChecker.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "address", "uint256", "uint256", "uint256"], + [ + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ] + ) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) }) }) - describe("internal checks", () => { + describe("check", () => { describe("pre check", () => { it("reverts when evidence invalid", async () => { - const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = + const { rewardNft, advancedChecker, target, subjectAddress, invalidEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) await expect( - advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") + advancedChecker.connect(target).check(subjectAddress, [invalidEncodedNFTId], 0) + ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") }) it("returns false when not owner", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = + const { advancedChecker, target, notOwnerAddress, validEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) + await advancedChecker.connect(target).check(notOwnerAddress, [validEncodedNFTId], 0) ).to.be.equal(false) }) it("succeeds when valid", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 0) ).to.be.equal(true) }) }) describe("main check", () => { it("returns false when balance insufficient", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = + const { advancedChecker, target, notOwnerAddress, validEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) + await advancedChecker.connect(target).check(notOwnerAddress, [validEncodedNFTId], 1) ).to.be.equal(false) }) it("succeeds when balance sufficient", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 1) ).to.be.equal(true) }) }) describe("post check", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = + it("reverts when already rewarded", async () => { + const { rewardNft, advancedChecker, target, subjectAddress, invalidEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) await rewardNft.mint(subjectAddress) expect( - await advancedCheckerHarness.connect(target).check(subjectAddress, [invalidNFTId], 2) + await advancedChecker.connect(target).check(subjectAddress, [invalidEncodedNFTId], 2) ).to.be.equal(false) }) it("succeeds when in valid range", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 2) ).to.be.equal(true) }) }) }) - - describe("internal checkPre", () => { - it("reverts when evidence invalid", async () => { - const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await expect( - advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") - }) - - it("returns false when not owner", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when valid", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) - - describe("internal checkMain", () => { - it("returns false when balance insufficient", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when balance sufficient", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) - - describe("internal checkPost", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await rewardNft.mint(subjectAddress) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when in valid range", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) }) describe("Policy", () => { @@ -338,62 +225,106 @@ describe("Advanced", () => { const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedERC721PolicyHarnessFactory: AdvancedERC721PolicyHarness__factory = - await ethers.getContractFactory("AdvancedERC721PolicyHarness") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt( "IERC721Errors", await signupNft.getAddress() ) - const rewardIERC721Errors: IERC721Errors = await ethers.getContractAt( - "IERC721Errors", - await rewardNft.getAddress() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer ) - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), 1, 0, 10 ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } - const advancedCheckerSkippedPrePostNoMultMain: AdvancedERC721Checker = - await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], - 1, - 0, - 10 - ) + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() - const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( + const advancedPolicyTx = await advancedPolicyFactory.deploy( await advancedChecker.getAddress(), false, false, true ) - const policySkipped: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await advancedCheckerSkippedPrePostNoMultMain.getAddress(), + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicSkippedTx = await advancedPolicyFactory.deploy( + await advancedChecker.getAddress(), true, true, false ) - const policyHarness: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( - deployer - ).deploy(await advancedChecker.getAddress(), false, false, true) - const policyHarnessSkipped: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( + const advancedPolicySkippedTxReceipt = await advancedPolicSkippedTx.wait() + const advancedPolicySkippedCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicySkippedTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicySkipped: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicySkippedCloneDeployedEvent.args.clone, deployer - ).deploy(await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false) + ) // mint 0 for subject. await signupNft.connect(deployer).mint(subjectAddress) @@ -403,21 +334,17 @@ describe("Advanced", () => { const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { - signupIERC721Errors, - rewardIERC721Errors, - AdvancedERC721PolicyFactory, + iERC721Errors, signupNft, rewardNft, + baseChecker, advancedChecker, - advancedCheckerSkippedPrePostNoMultMain, - policyHarness, - policyHarnessSkipped, - policy, - policySkipped, - subject, + advancedPolicy, + advancedPolicySkipped, deployer, target, notOwner, + subject, subjectAddress, notOwnerAddress, validEncodedNFTId, @@ -425,47 +352,94 @@ describe("Advanced", () => { } } - describe("constructor", () => { - it("deploys correctly", async () => { - const { policy } = await loadFixture(deployAdvancedPolicyFixture) + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { + const { advancedPolicy, advancedPolicySkipped } = await loadFixture(deployAdvancedPolicyFixture) + + expect(advancedPolicy).to.not.eq(undefined) + expect(await advancedPolicy.initialized()).to.be.eq(true) + expect(advancedPolicySkipped).to.not.eq(undefined) + expect(await advancedPolicySkipped.initialized()).to.be.eq(true) + }) - expect(policy).to.not.eq(undefined) + it("should revert when already initialized", async () => { + const { advancedPolicy, advancedPolicySkipped, deployer } = + await loadFixture(deployAdvancedPolicyFixture) + + await expect(advancedPolicy.connect(deployer).initialize()).to.be.revertedWithCustomError( + advancedPolicy, + "AlreadyInitialized" + ) + await expect(advancedPolicySkipped.connect(deployer).initialize()).to.be.revertedWithCustomError( + advancedPolicySkipped, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { advancedPolicy, advancedPolicySkipped, advancedChecker, deployer } = + await loadFixture(deployAdvancedPolicyFixture) + + const appendedBytes = await advancedPolicy.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "bool", "bool", "bool"], + [await deployer.getAddress(), await advancedChecker.getAddress(), false, false, true] + ) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) + + const appendedBytesSkipped = await advancedPolicySkipped.getAppendedBytes.staticCall() + + const expectedBytesSkipped = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "bool", "bool", "bool"], + [await deployer.getAddress(), await advancedChecker.getAddress(), true, true, false] + ) + .toLowerCase() + + expect(appendedBytesSkipped).to.equal(expectedBytesSkipped) }) }) describe("trait", () => { it("returns correct value", async () => { - const { policy } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, advancedPolicySkipped } = await loadFixture(deployAdvancedPolicyFixture) - expect(await policy.trait()).to.be.eq("AdvancedERC721") + expect(await advancedPolicy.trait()).to.be.eq("AdvancedERC721") + expect(await advancedPolicySkipped.trait()).to.be.eq("AdvancedERC721") }) }) describe("setTarget", () => { it("reverts when caller not owner", async () => { - const { policy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) await expect( - policy.connect(notOwner).setTarget(await target.getAddress()) - ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") + advancedPolicy.connect(notOwner).setTarget(await target.getAddress()) + ).to.be.revertedWithCustomError(advancedPolicy, "OwnableUnauthorizedAccount") }) it("reverts when zero address", async () => { - const { policy, deployer } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, deployer } = await loadFixture(deployAdvancedPolicyFixture) - await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( - policy, + await expect(advancedPolicy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( + advancedPolicy, "ZeroAddress" ) }) it("sets target correctly", async () => { - const { policy, target, AdvancedERC721PolicyFactory } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, target } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - const tx = await policy.setTarget(targetAddress) + const tx = await advancedPolicy.setTarget(targetAddress) const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( + const event = advancedPolicy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -475,74 +449,77 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.target).to.eq(targetAddress) - expect(await policy.getTarget()).to.eq(targetAddress) }) it("reverts when already set", async () => { - const { policy, target } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, target } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - await policy.setTarget(targetAddress) + await advancedPolicy.setTarget(targetAddress) - await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") + await expect(advancedPolicy.setTarget(targetAddress)).to.be.revertedWithCustomError( + advancedPolicy, + "TargetAlreadySet" + ) }) }) describe("enforce", () => { describe("pre check", () => { it("reverts when caller not target", async () => { - const { policy, subject, target, subjectAddress } = await loadFixture(deployAdvancedPolicyFixture) + const { advancedPolicy, subject, target, subjectAddress } = + await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) await expect( - policy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) - ).to.be.revertedWithCustomError(policy, "TargetOnly") + advancedPolicy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") }) it("reverts when evidence invalid", async () => { - const { rewardIERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = + const { iERC721Errors, advancedPolicy, target, subjectAddress, invalidEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) await expect( - policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 0) - ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") + advancedPolicy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 0) + ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") }) it("reverts when pre-check skipped", async () => { - const { policySkipped, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicySkipped, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policySkipped.setTarget(await target.getAddress()) + await advancedPolicySkipped.setTarget(await target.getAddress()) await expect( - policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policySkipped, "CannotPreCheckWhenSkipped") + advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "CannotPreCheckWhenSkipped") }) it("reverts when check unsuccessful", async () => { - const { policy, target, notOwnerAddress, validEncodedNFTId } = + const { advancedPolicy, target, notOwnerAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") }) it("enforces pre-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - await policy.setTarget(targetAddress) + await advancedPolicy.setTarget(targetAddress) - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( + const event = advancedPolicy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -558,58 +535,58 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(0) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) }) it("reverts when pre already enforced", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) await expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "AlreadyEnforced") }) }) describe("main check", () => { it("reverts when pre-check missing", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicy, "PreCheckNotEnforced") }) it("reverts when check unsuccessful", async () => { - const { policy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") }) it("enforces main-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( + const event = advancedPolicy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -625,21 +602,21 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1) }) it("executes multiple mains when allowed", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - await policy.setTarget(targetAddress) + await advancedPolicy.setTarget(targetAddress) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( + const event = advancedPolicy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -655,105 +632,111 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(2) }) - it("executes multiple mains when allowed", async () => { - const { policySkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = + it("reverts when main check already enfored", async () => { + const { advancedPolicySkipped, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policySkipped.setTarget(await target.getAddress()) - await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicySkipped.setTarget(await target.getAddress()) + await advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) expect( - policySkipped.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policySkipped, "MainCheckAlreadyEnforced") + advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "MainCheckAlreadyEnforced") }) }) describe("post check", () => { it("reverts when pre/main missing", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) + await advancedPolicy.setTarget(await target.getAddress()) expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "PreCheckNotEnforced") - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "MainCheckNotEnforced") + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "MainCheckNotEnforced") }) it("reverts when caller not target", async () => { - const { policy, subject, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, subject, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) await expect( - policy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) - ).to.be.revertedWithCustomError(policy, "TargetOnly") + advancedPolicy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") }) it("reverts when already rewarded", async () => { - const { rewardNft, policy, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + const { + rewardNft, + advancedPolicy, + target, + subjectAddress, + validEncodedNFTId, + invalidEncodedNFTId + } = await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) await rewardNft.mint(subjectAddress) await expect( - policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") + advancedPolicy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") }) it("reverts when post-check skipped", async () => { - const { policySkipped, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicySkipped, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policySkipped.setTarget(await target.getAddress()) - await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicySkipped.setTarget(await target.getAddress()) + await advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) await expect( - policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policySkipped, "CannotPostCheckWhenSkipped") + advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "CannotPostCheckWhenSkipped") }) it("reverts when check unsuccessful", async () => { - const { policy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") }) it("enforces post-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) const targetAddress = await target.getAddress() - await policy.setTarget(targetAddress) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.setTarget(targetAddress) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( + const event = advancedPolicy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -769,325 +752,21 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(2) - expect((await policy.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) - }) - - it("reverts when post already enforced", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - - await expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") - }) - }) - }) - - describe("internal enforce", () => { - describe("internal pre", () => { - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 0) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { rewardIERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId], 0) - ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when pre-check skipped", async () => { - const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - - await expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPreCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces pre-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(targetAddress) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - }) - - it("reverts when pre already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") - }) - }) - - describe("_main", () => { - it("reverts when pre-check missing", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces main-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1) - }) - - it("executes multiple mains when allowed", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - await policyHarness.setTarget(targetAddress) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2) - }) - - it("executes multiple mains when allowed", async () => { - const { policyHarnessSkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policyHarnessSkipped.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") - expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") - }) - }) - - describe("_post", () => { - it("reverts when pre/main missing", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "MainCheckNotEnforced") - }) - - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 2) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { rewardNft, policyHarness, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await rewardNft.mint(subjectAddress) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("reverts when post-check skipped", async () => { - const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPostCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces post-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(targetAddress) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[2]).to.be.equal(true) }) it("reverts when post already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "AlreadyEnforced") }) }) }) @@ -1095,76 +774,117 @@ describe("Advanced", () => { describe("Voting", () => { async function deployAdvancedVotingFixture() { - const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + const AdvancedVoting: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt( "IERC721Errors", await signupNft.getAddress() ) - const rewardIERC721Errors: IERC721Errors = await ethers.getContractAt( - "IERC721Errors", - await rewardNft.getAddress() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer ) - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), 1, 0, 10 ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) - const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() + + const advancedPolicyTx = await advancedPolicyFactory.deploy( await advancedChecker.getAddress(), false, false, true ) + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) - const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( - await policy.getAddress() + const advancedVoting: AdvancedVoting = await AdvancedVoting.connect(deployer).deploy( + await advancedPolicy.getAddress() ) // mint 0 for subject. await signupNft.connect(deployer).mint(subjectAddress) // encoded token ids. - const validNFTId = 0 - const invalidNFTId = 1 - const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) - const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { - signupIERC721Errors, - rewardIERC721Errors, - AdvancedVotingFactory, - AdvancedERC721PolicyFactory, + iERC721Errors, signupNft, rewardNft, + baseChecker, advancedChecker, - voting, - policy, - subject, + advancedPolicy, + advancedVoting, deployer, + target, notOwner, + subject, subjectAddress, notOwnerAddress, - validNFTId, - invalidNFTId, validEncodedNFTId, invalidEncodedNFTId } @@ -1172,57 +892,56 @@ describe("Advanced", () => { describe("constructor", () => { it("deploys correctly", async () => { - const { voting } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting } = await loadFixture(deployAdvancedVotingFixture) - expect(voting).to.not.eq(undefined) + expect(advancedVoting).to.not.eq(undefined) }) }) describe("register", () => { it("reverts when caller not target", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await notOwner.getAddress()) + await advancedPolicy.setTarget(await notOwner.getAddress()) - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "TargetOnly" - ) + await expect( + advancedVoting.connect(notOwner).register(validEncodedNFTId) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") }) it("reverts when evidence invalid", async () => { - const { signupIERC721Errors, voting, policy, subject, invalidNFTId } = + const { iERC721Errors, advancedVoting, advancedPolicy, subject, invalidEncodedNFTId } = await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) - await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( - signupIERC721Errors, - "ERC721NonexistentToken" - ) + await expect( + advancedVoting.connect(subject).register(invalidEncodedNFTId) + ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") }) it("reverts when check fails", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "UnsuccessfulCheck" - ) + await expect( + advancedVoting.connect(notOwner).register(validEncodedNFTId) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") }) it("registers successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId, subjectAddress } = await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() + const targetAddress = await advancedVoting.getAddress() - await policy.setTarget(targetAddress) + await advancedPolicy.setTarget(targetAddress) - const tx = await voting.connect(subject).register(validNFTId) + const tx = await advancedVoting.connect(subject).register(validEncodedNFTId) const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( + const event = advancedVoting.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -1232,22 +951,23 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(0n) - expect(await voting.voteCounts(0)).to.be.equal(0) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(0n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(0) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) }) it("reverts when already registered", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + const targetAddress = await advancedVoting.getAddress() - await policy.setTarget(targetAddress) + await advancedPolicy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) + await advancedVoting.connect(subject).register(validEncodedNFTId) - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( - policy, + await expect(advancedVoting.connect(subject).register(validEncodedNFTId)).to.be.revertedWithCustomError( + advancedPolicy, "AlreadyEnforced" ) }) @@ -1255,34 +975,41 @@ describe("Advanced", () => { describe("vote", () => { it("reverts when not registered", async () => { - const { voting, policy, subject } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, subject } = await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) - await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") + await expect(advancedVoting.connect(subject).vote(0)).to.be.revertedWithCustomError( + advancedVoting, + "NotRegistered" + ) }) it("reverts when option invalid", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) - await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") + await expect(advancedVoting.connect(subject).vote(3)).to.be.revertedWithCustomError( + advancedVoting, + "InvalidOption" + ) }) it("votes successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedVotingFixture) const option = 0 - const targetAddress = await voting.getAddress() + const targetAddress = await advancedVoting.getAddress() - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) - const tx = await voting.connect(subject).vote(option) + const tx = await advancedVoting.connect(subject).vote(option) const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( + const event = advancedVoting.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -1294,25 +1021,25 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) expect(event.args.option).to.eq(option) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1n) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(1) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) }) it("allows multiple votes", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedVotingFixture) const option = 0 - const targetAddress = await voting.getAddress() + const targetAddress = await advancedVoting.getAddress() - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(option) + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(option) - const tx = await voting.connect(subject).vote(option) + const tx = await advancedVoting.connect(subject).vote(option) const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( + const event = advancedVoting.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -1324,89 +1051,105 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) expect(event.args.option).to.eq(option) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2n) - expect(await voting.voteCounts(0)).to.be.equal(2) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(2n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(2) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) }) }) describe("eligibility", () => { it("reverts when caller not target", async () => { - const { voting, policy, subject, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, subject, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await notOwner.getAddress()) + await advancedPolicy.setTarget(await notOwner.getAddress()) - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( - policy, + await expect(advancedVoting.connect(subject).register(validEncodedNFTId)).to.be.revertedWithCustomError( + advancedPolicy, "TargetOnly" ) }) it("reverts when already owns reward token", async () => { - const { rewardNft, voting, policy, subject, validNFTId } = + const { rewardNft, advancedVoting, advancedPolicy, subject, validEncodedNFTId } = await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) await rewardNft.mint(subject) - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( - policy, + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedPolicy, "UnsuccessfulCheck" ) }) it("reverts when check fails", async () => { - const { signupNft, rewardNft, deployer, voting, policy, notOwner, subject, validNFTId } = - await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) + const { + signupNft, + rewardNft, + deployer, + advancedVoting, + advancedPolicy, + notOwner, + subject, + validEncodedNFTId + } = await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) await signupNft.connect(deployer).mint(notOwner) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) - await voting.connect(notOwner).register(1) - await voting.connect(notOwner).vote(0) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + await advancedVoting.connect(notOwner).register(1) + await advancedVoting.connect(notOwner).vote(0) await rewardNft.connect(deployer).mint(subject) - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( - policy, + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedPolicy, "UnsuccessfulCheck" ) }) it("reverts when not registered", async () => { - const { voting, policy, notOwner } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, notOwner } = await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await notOwner.getAddress()) + await advancedPolicy.setTarget(await notOwner.getAddress()) - await expect(voting.connect(notOwner).eligible()).to.be.revertedWithCustomError(voting, "NotRegistered") + await expect(advancedVoting.connect(notOwner).eligible()).to.be.revertedWithCustomError( + advancedVoting, + "NotRegistered" + ) }) it("reverts when not voted", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError(voting, "NotVoted") + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedVoting, + "NotVoted" + ) }) it("verifies eligibility successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() + const targetAddress = await advancedVoting.getAddress() - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) - const tx = await voting.connect(subject).eligible() + const tx = await advancedVoting.connect(subject).eligible() const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( + const event = advancedVoting.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -1416,68 +1159,118 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1n) - expect((await policy.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1n) + expect((await advancedPolicy.enforced(subjectAddress))[2]).to.be.equal(true) + expect(await advancedVoting.voteCounts(0)).to.be.equal(1) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) }) it("reverts when already eligible", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) - await voting.connect(subject).eligible() + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + await advancedVoting.connect(subject).eligible() - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( - voting, + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedVoting, "AlreadyEligible" ) }) }) + describe("end to end", () => { it("completes full voting lifecycle", async () => { const [deployer]: Signer[] = await ethers.getSigners() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect( + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + const AdvancedVoting: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, deployer - ).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), 1, 0, 10 ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() - const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( + const advancedPolicyTx = await advancedPolicyFactory.deploy( await advancedChecker.getAddress(), false, false, true ) + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) - const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( - await policy.getAddress() + const advancedVoting: AdvancedVoting = await AdvancedVoting.connect(deployer).deploy( + await advancedPolicy.getAddress() ) // set the target. - const targetAddress = await voting.getAddress() - await policy.setTarget(targetAddress) + const targetAddress = await advancedVoting.getAddress() + await advancedPolicy.setTarget(targetAddress) for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { const voterAddress = await voter.getAddress() @@ -1486,17 +1279,17 @@ describe("Advanced", () => { await signupNft.connect(deployer).mint(voterAddress) // register. - await voting.connect(voter).register(tokenId) + await advancedVoting.connect(voter).register(tokenId) // vote. - await voting.connect(voter).vote(tokenId % 2) + await advancedVoting.connect(voter).vote(tokenId % 2) // reward. - await voting.connect(voter).eligible() + await advancedVoting.connect(voter).eligible() - expect((await policy.enforced(targetAddress, voterAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, voterAddress))[1]).to.be.equal(1) - expect((await policy.enforced(targetAddress, voterAddress))[2]).to.be.equal(true) + expect((await advancedPolicy.enforced(voterAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(voterAddress))[1]).to.be.equal(1) + expect((await advancedPolicy.enforced(voterAddress))[2]).to.be.equal(true) } }) }) diff --git a/packages/contracts/test/Base.test.ts b/packages/contracts/test/Base.test.ts index 21f6fdc..a1e7237 100644 --- a/packages/contracts/test/Base.test.ts +++ b/packages/contracts/test/Base.test.ts @@ -1,23 +1,24 @@ -import { expect } from "chai" -import { ethers } from "hardhat" import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" +import { ethers } from "hardhat" +import { expect } from "chai" import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" import { - BaseERC721Checker, + NFT__factory, BaseERC721Checker__factory, - BaseERC721Policy, BaseERC721Policy__factory, - BaseERC721CheckerHarness, - BaseERC721CheckerHarness__factory, - BaseERC721PolicyHarness, - BaseERC721PolicyHarness__factory, + BaseERC721CheckerFactory__factory, + BaseERC721PolicyFactory__factory, NFT, - NFT__factory, + BaseERC721Checker, + BaseERC721Policy, + BaseERC721CheckerFactory, + BaseERC721PolicyFactory, IERC721Errors, BaseVoting__factory, BaseVoting } from "../typechain-types" +/* eslint-disable @typescript-eslint/no-shadow */ describe("Base", () => { describe("Checker", () => { async function deployBaseCheckerFixture() { @@ -25,123 +26,99 @@ describe("Base", () => { const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721CheckerHarnessFactory: BaseERC721CheckerHarness__factory = - await ethers.getContractFactory("BaseERC721CheckerHarness") - - const nft: NFT = await NFTFactory.deploy() - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const checkerHarness: BaseERC721CheckerHarness = await BaseERC721CheckerHarnessFactory.connect( - deployer - ).deploy([await nft.getAddress()]) + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + + const nft: NFT = await NFT.deploy() + const factory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + + const tx = await factory.deploy(await nft.getAddress()) + const receipt = await tx.wait() + const event = BaseERC721CheckerFactory.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect(event.args.clone, deployer) // mint 0 for subject. await nft.connect(deployer).mint(subjectAddress) // encoded token ids. - const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { nft, checker, - checkerHarness, + factory, + deployer, target, subjectAddress, notOwnerAddress, - validNFTId, - invalidNFTId + validEncodedNFTId, + invalidEncodedNFTId } } - describe("constructor", () => { - it("deploys correctly", async () => { + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { const { checker } = await loadFixture(deployBaseCheckerFixture) expect(checker).to.not.eq(undefined) + expect(await checker.initialized()).to.be.eq(true) }) - }) - - describe("getVerifierAtIndex", () => { - it("returns correct verifier address", async () => { - const { checker, nft } = await loadFixture(deployBaseCheckerFixture) - expect(await checker.getVerifierAtIndex(0)).to.equal(await nft.getAddress()) - }) - - it("reverts when index out of bounds", async () => { - const { checker } = await loadFixture(deployBaseCheckerFixture) - await expect(checker.getVerifierAtIndex(1)).to.be.revertedWithCustomError(checker, "VerifierNotFound") - }) - }) - describe("internal getVerifierAtIndex", () => { - it("returns correct verifier address", async () => { - const { checkerHarness, nft } = await loadFixture(deployBaseCheckerFixture) - expect(await checkerHarness.exposed__getVerifierAtIndex(0)).to.equal(await nft.getAddress()) - }) + it("should revert when already initialized", async () => { + const { checker, deployer } = await loadFixture(deployBaseCheckerFixture) - it("reverts when index out of bounds", async () => { - const { checkerHarness } = await loadFixture(deployBaseCheckerFixture) - await expect(checkerHarness.exposed__getVerifierAtIndex(1)).to.be.revertedWithCustomError( - checkerHarness, - "VerifierNotFound" + await expect(checker.connect(deployer).initialize()).to.be.revertedWithCustomError( + checker, + "AlreadyInitialized" ) }) }) - describe("check", () => { - it("reverts when evidence is invalid", async () => { - const { nft, checker, target, subjectAddress, invalidNFTId } = - await loadFixture(deployBaseCheckerFixture) - - await expect( - checker.connect(target).check(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { checker, nft } = await loadFixture(deployBaseCheckerFixture) - it("returns false when subject not owner", async () => { - const { checker, target, notOwnerAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) + const appendedBytes = await checker.getAppendedBytes.staticCall() - expect(await checker.connect(target).check(notOwnerAddress, [validNFTId])).to.be.equal(false) - }) - - it("succeeds when valid", async () => { - const { checker, target, subjectAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode(["address"], [await nft.getAddress()]) + .toLowerCase() - expect(await checker.connect(target).check(subjectAddress, [validNFTId])).to.be.equal(true) + expect(appendedBytes).to.equal(expectedBytes) }) }) - describe("internal check", () => { + describe("check", () => { it("reverts when evidence is invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = + const { nft, checker, target, subjectAddress, invalidEncodedNFTId } = await loadFixture(deployBaseCheckerFixture) await expect( - checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId]) + checker.connect(target).check(subjectAddress, [invalidEncodedNFTId]) ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") }) it("returns false when subject not owner", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = + const { checker, target, notOwnerAddress, validEncodedNFTId } = await loadFixture(deployBaseCheckerFixture) - expect(await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId])).to.be.equal( - false - ) + expect(await checker.connect(target).check(notOwnerAddress, [validEncodedNFTId])).to.be.equal(false) }) it("succeeds when valid", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { checker, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBaseCheckerFixture) - expect(await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId])).to.be.equal( - true - ) + expect(await checker.connect(target).check(subjectAddress, [validEncodedNFTId])).to.be.equal(true) }) }) }) @@ -152,26 +129,47 @@ describe("Base", () => { const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseERC721PolicyHarnessFactory: BaseERC721PolicyHarness__factory = - await ethers.getContractFactory("BaseERC721PolicyHarness") + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") - const nft: NFT = await NFTFactory.deploy() + const nft: NFT = await NFT.deploy() const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() + const checkerFactory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() + + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer ) - const policyHarness: BaseERC721PolicyHarness = await BaseERC721PolicyHarnessFactory.connect( + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, deployer - ).deploy(await checker.getAddress()) + ) as BaseERC721Policy // mint 0 for subject. await nft.connect(deployer).mint(subjectAddress) @@ -182,9 +180,8 @@ describe("Base", () => { return { iERC721Errors, - BaseERC721PolicyFactory, nft, - policyHarness, + checker, policy, subject, deployer, @@ -197,11 +194,35 @@ describe("Base", () => { } } - describe("constructor", () => { - it("deploys correctly", async () => { + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { const { policy } = await loadFixture(deployBasePolicyFixture) expect(policy).to.not.eq(undefined) + expect(await policy.initialized()).to.be.eq(true) + }) + + it("should revert when already initialized", async () => { + const { policy, deployer } = await loadFixture(deployBasePolicyFixture) + + await expect(policy.connect(deployer).initialize()).to.be.revertedWithCustomError( + policy, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { policy, checker, deployer } = await loadFixture(deployBasePolicyFixture) + + const appendedBytes = await policy.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode(["address", "address"], [await deployer.getAddress(), await checker.getAddress()]) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) }) }) @@ -232,12 +253,11 @@ describe("Base", () => { }) it("sets target correctly", async () => { - const { policy, target, BaseERC721PolicyFactory } = await loadFixture(deployBasePolicyFixture) + const { policy, target } = await loadFixture(deployBasePolicyFixture) const targetAddress = await target.getAddress() - const tx = await policy.setTarget(targetAddress) const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( + const event = policy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -247,7 +267,6 @@ describe("Base", () => { expect(receipt?.status).to.eq(1) expect(event.args.target).to.eq(targetAddress) - expect(await policy.getTarget()).to.eq(targetAddress) }) it("reverts when already set", async () => { @@ -295,15 +314,14 @@ describe("Base", () => { }) it("enforces successfully", async () => { - const { BaseERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) + const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) const targetAddress = await target.getAddress() await policy.setTarget(await target.getAddress()) const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( + const event = policy.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -317,7 +335,7 @@ describe("Base", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policy.enforced(targetAddress, subjectAddress)).to.be.equal(true) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) }) it("reverts when already enforced", async () => { @@ -332,104 +350,58 @@ describe("Base", () => { ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") }) }) - - describe("internal enforce", () => { - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash]) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { iERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId]) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when check fails", async () => { - const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces successfully", async () => { - const { BaseERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(await target.getAddress()) - - const tx = await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) - const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policyHarness.enforced(targetAddress, subjectAddress)).to.be.equal(true) - }) - - it("reverts when already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") - }) - }) }) describe("Voting", () => { async function deployBaseVotingFixture() { - const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() const subjectAddress: string = await subject.getAddress() const notOwnerAddress: string = await notOwner.getAddress() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") + const BaseVoting: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") - const nft: NFT = await NFTFactory.deploy() + const nft: NFT = await NFT.deploy() const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() + const checkerFactory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() + + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer ) - const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, + deployer + ) as BaseERC721Policy + + const baseVoting: BaseVoting = await BaseVoting.connect(deployer).deploy(await policy.getAddress()) // mint 0 for subject. await nft.connect(deployer).mint(subjectAddress) @@ -442,12 +414,13 @@ describe("Base", () => { return { iERC721Errors, - BaseVotingFactory, nft, - voting, + checker, policy, + baseVoting, subject, deployer, + target, notOwner, subjectAddress, notOwnerAddress, @@ -460,57 +433,59 @@ describe("Base", () => { describe("constructor", () => { it("deploys correctly", async () => { - const { voting } = await loadFixture(deployBaseVotingFixture) + const { baseVoting, subject } = await loadFixture(deployBaseVotingFixture) - expect(voting).to.not.eq(undefined) + expect(baseVoting).to.not.eq(undefined) + expect(await baseVoting.hasVoted(subject)).to.be.eq(false) + expect(await baseVoting.voteCounts(0)).to.be.eq(0) }) }) describe("register", () => { it("reverts when caller not target", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + const { baseVoting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) await policy.setTarget(await notOwner.getAddress()) - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( + await expect(baseVoting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( policy, "TargetOnly" ) }) it("reverts when evidence invalid", async () => { - const { iERC721Errors, voting, policy, subject, invalidNFTId } = + const { iERC721Errors, baseVoting, policy, subject, invalidNFTId } = await loadFixture(deployBaseVotingFixture) - await policy.setTarget(await voting.getAddress()) + await policy.setTarget(await baseVoting.getAddress()) - await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( + await expect(baseVoting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( iERC721Errors, "ERC721NonexistentToken" ) }) it("reverts when check fails", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + const { baseVoting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) - await policy.setTarget(await voting.getAddress()) + await policy.setTarget(await baseVoting.getAddress()) - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( + await expect(baseVoting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( policy, "UnsuccessfulCheck" ) }) it("registers successfully", async () => { - const { BaseVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = + const { baseVoting, policy, subject, validNFTId, subjectAddress } = await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() + const targetAddress = await baseVoting.getAddress() await policy.setTarget(targetAddress) - const tx = await voting.connect(subject).register(validNFTId) + const tx = await baseVoting.connect(subject).register(validNFTId) const receipt = await tx.wait() - const event = BaseVotingFactory.interface.parseLog( + const event = baseVoting.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -520,21 +495,21 @@ describe("Base", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect(await policy.enforced(targetAddress, subjectAddress)).to.be.equal(true) - expect(await voting.hasVoted(subjectAddress)).to.be.equal(false) - expect(await voting.voteCounts(0)).to.be.equal(0) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) + expect(await baseVoting.hasVoted(subjectAddress)).to.be.equal(false) + expect(await baseVoting.voteCounts(0)).to.be.equal(0) + expect(await baseVoting.voteCounts(1)).to.be.equal(0) }) it("reverts when already registered", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + const targetAddress = await baseVoting.getAddress() await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) + await baseVoting.connect(subject).register(validNFTId) - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( + await expect(baseVoting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( policy, "AlreadyEnforced" ) @@ -543,33 +518,39 @@ describe("Base", () => { describe("vote", () => { it("reverts when not registered", async () => { - const { voting, policy, subject } = await loadFixture(deployBaseVotingFixture) + const { baseVoting, policy, subject } = await loadFixture(deployBaseVotingFixture) - await policy.setTarget(await voting.getAddress()) + await policy.setTarget(await baseVoting.getAddress()) - await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") + await expect(baseVoting.connect(subject).vote(0)).to.be.revertedWithCustomError( + baseVoting, + "NotRegistered" + ) }) it("reverts when option invalid", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) + await policy.setTarget(await baseVoting.getAddress()) + await baseVoting.connect(subject).register(validNFTId) - await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") + await expect(baseVoting.connect(subject).vote(3)).to.be.revertedWithCustomError( + baseVoting, + "InvalidOption" + ) }) it("votes successfully", async () => { - const { BaseVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = + const { baseVoting, policy, subject, subjectAddress, validNFTId } = await loadFixture(deployBaseVotingFixture) const option = 0 - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) + await policy.setTarget(await baseVoting.getAddress()) + await baseVoting.connect(subject).register(validNFTId) - const tx = await voting.connect(subject).vote(option) + const tx = await baseVoting.connect(subject).vote(option) const receipt = await tx.wait() - const event = BaseVotingFactory.interface.parseLog( + const event = baseVoting.interface.parseLog( receipt?.logs[0] as unknown as { topics: string[]; data: string } ) as unknown as { args: { @@ -581,21 +562,24 @@ describe("Base", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) expect(event.args.option).to.eq(option) - expect(await voting.hasVoted(subjectAddress)).to.be.equal(true) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) + expect(await baseVoting.hasVoted(subjectAddress)).to.be.equal(true) + expect(await baseVoting.voteCounts(0)).to.be.equal(1) + expect(await baseVoting.voteCounts(1)).to.be.equal(0) }) it("reverts when already voted", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + const targetAddress = await baseVoting.getAddress() await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) + await baseVoting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) + await baseVoting.connect(subject).vote(0) - await expect(voting.connect(subject).vote(1)).to.be.revertedWithCustomError(voting, "AlreadyVoted") + await expect(baseVoting.connect(subject).vote(1)).to.be.revertedWithCustomError( + baseVoting, + "AlreadyVoted" + ) }) }) @@ -603,25 +587,53 @@ describe("Base", () => { it("completes full voting flow", async () => { const [deployer]: Signer[] = await ethers.getSigners() - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") + const BaseVoting: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + + const nft: NFT = await NFT.deploy() - const nft: NFT = await NFTFactory.deploy() + const checkerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer ) - const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, + deployer + ) as BaseERC721Policy + + const baseVoting: BaseVoting = await BaseVoting.connect(deployer).deploy(await policy.getAddress()) // set the target. - await policy.setTarget(await voting.getAddress()) + await policy.setTarget(await baseVoting.getAddress()) for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { const voterAddress = await voter.getAddress() @@ -630,12 +642,12 @@ describe("Base", () => { await nft.connect(deployer).mint(voterAddress) // register. - await voting.connect(voter).register(tokenId) + await baseVoting.connect(voter).register(tokenId) // vote. - await voting.connect(voter).vote(tokenId % 2) + await baseVoting.connect(voter).vote(tokenId % 2) - expect(await voting.hasVoted(voter)).to.be.equal(true) + expect(await baseVoting.hasVoted(voter)).to.be.equal(true) } }) }) diff --git a/yarn.lock b/yarn.lock index ad764ce..1e0168a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3107,6 +3107,7 @@ __metadata: resolution: "@excubiae/contracts@workspace:packages/contracts/contracts" dependencies: "@openzeppelin/contracts": "npm:^5.0.2" + solady: "npm:^0.0.298" languageName: unknown linkType: soft @@ -9394,6 +9395,7 @@ __metadata: forge-std: "github:foundry-rs/forge-std#v1.9.2" hardhat: "npm:^2.22.11" hardhat-gas-reporter: "npm:^1.0.8" + solady: "npm:^0.0.298" solhint: "npm:^5.0.3" solidity-coverage: "npm:^0.8.13" ts-node: "npm:^10.9.2" @@ -17724,6 +17726,13 @@ __metadata: languageName: node linkType: hard +"solady@npm:^0.0.298": + version: 0.0.298 + resolution: "solady@npm:0.0.298" + checksum: 10/b337d65e0f3aadabcb981a6bbf71a4097cc9e0f91afb354a77352a45d933109834568c5e3e3c90c30a29809b95aeb0eb8da4574dbffa18ff451064f52a0fa4a0 + languageName: node + linkType: hard + "solc@npm:0.8.26": version: 0.8.26 resolution: "solc@npm:0.8.26"