Skip to content

Commit

Permalink
test(contracts): add advanced ts tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xjei committed Nov 28, 2024
1 parent 39b1b30 commit ab8f3fa
Show file tree
Hide file tree
Showing 11 changed files with 1,782 additions and 23 deletions.
11 changes: 7 additions & 4 deletions packages/contracts/contracts/src/AdvancedChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ abstract contract AdvancedChecker is IAdvancedChecker {
/// @param evidence The evidence associated with the check.
/// @param checkType The type of check to perform (PRE, MAIN, POST).
function _check(address subject, bytes memory evidence, Check checkType) internal view returns (bool checked) {
if (skipPre && checkType == Check.PRE) revert PreCheckSkipped();
if (skipPost && checkType == Check.POST) revert PostCheckSkipped();

if (!skipPre && checkType == Check.PRE) {
return _checkPre(subject, evidence);
} else if (!skipPost && checkType == Check.POST) {
}

if (!skipPost && checkType == Check.POST) {
return _checkPost(subject, evidence);
} else if (checkType == Check.MAIN) {
return _checkMain(subject, evidence);
}

return false;
return _checkMain(subject, evidence);
}

/// @notice Internal method for performing pre-condition checks.
Expand Down
48 changes: 36 additions & 12 deletions packages/contracts/contracts/src/AdvancedPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,45 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy {
function _enforce(address subject, bytes calldata evidence, Check checkType) internal {
bool checked = ADVANCED_CHECKER.check(subject, evidence, checkType);

if (!checked) revert UnsuccessfulCheck();
if (!checked) {
revert UnsuccessfulCheck();
}

if (checkType == Check.PRE) {
if (ADVANCED_CHECKER.skipPre()) revert PreCheckSkipped();
else if (enforced[msg.sender][subject].pre) revert AlreadyEnforced();
else enforced[msg.sender][subject].pre = true;
} else if (checkType == Check.POST) {
if (ADVANCED_CHECKER.skipPost()) revert PostCheckSkipped();
else if (enforced[msg.sender][subject].post) revert AlreadyEnforced();
else enforced[msg.sender][subject].post = true;
} else if (checkType == Check.MAIN) {
if (!ADVANCED_CHECKER.allowMultipleMain() && enforced[msg.sender][subject].main > 0) {
revert MainCheckAlreadyEnforced();
if (!ADVANCED_CHECKER.skipPost() && enforced[msg.sender][subject].pre) {
revert AlreadyEnforced();
} else {
enforced[msg.sender][subject].pre = true;
}
} else {
if (checkType == Check.POST) {
if (enforced[msg.sender][subject].post) {
revert AlreadyEnforced();
} else {
if (!ADVANCED_CHECKER.skipPre() && !enforced[msg.sender][subject].pre) {
revert PreCheckNotEnforced();
} else {
if (enforced[msg.sender][subject].main == 0) {
revert MainCheckNotEnforced();
} else {
enforced[msg.sender][subject].post = true;
}
}
}
} else {
enforced[msg.sender][subject].main += 1;
if (
checkType == Check.MAIN &&
!ADVANCED_CHECKER.allowMultipleMain() &&
enforced[msg.sender][subject].main > 0
) {
revert MainCheckAlreadyEnforced();
} else {
if (checkType == Check.MAIN && !ADVANCED_CHECKER.skipPre() && !enforced[msg.sender][subject].pre) {
revert PreCheckNotEnforced();
} else {
enforced[msg.sender][subject].main += 1;
}
}
}
}

Expand Down
2 changes: 0 additions & 2 deletions packages/contracts/contracts/src/BaseChecker.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {IBaseChecker} from "./interfaces/IBaseChecker.sol";
/// It defines a method `check` that invokes a protected `_check` method, which must be implemented by derived
/// contracts.
abstract contract BaseChecker is IBaseChecker {
constructor() {}

/// @notice Checks the validity of the provided evidence for a given address.
/// @param subject The address to be checked.
/// @param evidence The evidence associated with the check.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ enum Check {
/// @title IAdvancedChecker.
/// @notice AdvancedChecker contract interface.
interface IAdvancedChecker {
/// @notice Error thrown when the PRE check is skipped.
error PreCheckSkipped();
/// @notice Error thrown when the POST check is skipped.
error PostCheckSkipped();

/// @dev Defines the custom `target` protection logic.
/// @param subject The address of the entity attempting to interact with the `target`.
/// @param evidence Additional data that may be required for the check.
Expand Down
10 changes: 5 additions & 5 deletions packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {Check} from "./IAdvancedChecker.sol";
/// @title IAdvancedPolicy
/// @notice IAdvancedPolicy contract interface that extends the IPolicy interface.
interface IAdvancedPolicy is IPolicy {
/// @notice Error thrown when the PRE check is skipped.
error PreCheckSkipped();

/// @notice Error thrown when the MAIN check cannot be executed more than once.
error MainCheckAlreadyEnforced();

/// @notice Error thrown when the POST check is skipped.
error PostCheckSkipped();
/// @notice Error thrown when the PRE check has not been enforced yet.
error PreCheckNotEnforced();

/// @notice Error thrown when the MAIN check has not been enforced yet.
error MainCheckNotEnforced();

/// @notice Event emitted when someone enforces the `target` check.
/// @param subject The address of those who have successfully enforced the check.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

/**
* @title AdvancedERC721Checker
* @notice Implements advanced checks for ERC721 token requirements.
* @dev Extends AdvancedChecker to provide three-phase validation:
* - Pre-check: Basic token ownership verification.
* - Main check: Token balance threshold validation.
* - Post-check: Special token ID range verification.
*/
contract AdvancedERC721Checker is AdvancedChecker {
IERC721 public immutable NFT;
/// @notice Minimum token balance required for main check.
uint256 public immutable MIN_BALANCE;
/// @notice Minimum token ID allowed for post-check validation.
uint256 public immutable MIN_TOKEN_ID;
/// @notice Maximum token ID allowed for post-check validation.
uint256 public immutable MAX_TOKEN_ID;

constructor(
IERC721 _nft,
uint256 _minBalance,
uint256 _minTokenId,
uint256 _maxTokenId,
bool _skipPre,
bool _skipPost,
bool _allowMultipleMain
) AdvancedChecker(_skipPre, _skipPost, _allowMultipleMain) {
NFT = _nft;
MIN_BALANCE = _minBalance;
MIN_TOKEN_ID = _minTokenId;
MAX_TOKEN_ID = _maxTokenId;
}

/**
* @notice Pre-check verifies basic token ownership.
* @dev Validates if the subject owns the specific tokenId provided in evidence.
* @param subject Address to check ownership for.
* @param evidence Encoded uint256 tokenId.
* @return True if subject owns the token, false otherwise.
*/
function _checkPre(address subject, bytes memory evidence) internal view override returns (bool) {
super._checkPre(subject, evidence);

uint256 tokenId = abi.decode(evidence, (uint256));
return NFT.ownerOf(tokenId) == subject;
}

/**
* @notice Main check verifies minimum token balance.
* @dev Validates if the subject holds at least MIN_BALANCE tokens.
* @param subject Address to check balance for.
* @param evidence Not used in this check.
* @return True if subject meets minimum balance requirement.
*/
function _checkMain(address subject, bytes memory evidence) internal view override returns (bool) {
super._checkMain(subject, evidence);

return NFT.balanceOf(subject) >= MIN_BALANCE;
}

/**
* @notice Post-check verifies ownership of a token within specific ID range.
* @dev Validates if subject owns a token with ID between MIN_TOKEN_ID and MAX_TOKEN_ID.
* @param subject Address to check ownership for.
* @param evidence Encoded uint256 tokenId.
* @return True if subject owns a token in valid range.
*/
function _checkPost(address subject, bytes memory evidence) internal view override returns (bool) {
super._checkPost(subject, evidence);

uint256 tokenId = abi.decode(evidence, (uint256));
return tokenId >= MIN_TOKEN_ID && tokenId <= MAX_TOKEN_ID && NFT.ownerOf(tokenId) == subject;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

/**
* @title AdvancedERC721Policy
* @notice Policy contract implementing three-phase validation for ERC721 tokens.
* @dev Extends AdvancedPolicy to enforce ERC721-specific checks through AdvancedERC721Checker.
*/
contract AdvancedERC721Policy is AdvancedPolicy {
/// @notice Reference to the ERC721 checker contract implementing validation logic.
AdvancedERC721Checker public immutable CHECKER;

/// @param _checker Address of the AdvancedERC721Checker contract.
constructor(AdvancedERC721Checker _checker) AdvancedPolicy(_checker) {
CHECKER = _checker;
}

/// @notice Returns the trait identifier for this policy.
/// @return String identifying this as an ERC721-based policy.
function trait() external pure returns (string memory) {
return "AdvancedERC721";
}
}
84 changes: 84 additions & 0 deletions packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

/**
* @title AdvancedVoting
* @notice Voting contract with three-phase validation and NFT rewards.
* @dev Uses pre-check for registration, main check for voting, and post-check for claiming NFT rewards.
*/
contract AdvancedVoting {
event Registered(address voter);
event Voted(address voter, uint8 option);
event RewardClaimed(address voter, uint256 rewardId);

error NotRegistered();
error NotVoted();
error AlreadyClaimed();
error InvalidOption();
error NotOwnerOfReward();

/// @notice Policy contract handling validation checks.
AdvancedPolicy public immutable POLICY;

/// @notice Tracks vote counts for each option.
mapping(uint8 => uint256) public voteCounts;

constructor(AdvancedPolicy _policy) {
POLICY = _policy;
}

/**
* @notice Register to participate in voting.
* @dev Validates NFT ownership through pre-check.
* @param tokenId Token ID to verify ownership.
*/
function register(uint256 tokenId) external {
bytes memory evidence = abi.encode(tokenId);

POLICY.enforce(msg.sender, evidence, Check.PRE);

emit Registered(msg.sender);
}

/**
* @notice Cast vote after verifying registration.
* @dev Requires pre-check completion and validates voting power.
* @param option Voting option (0 or 1).
*/
function vote(uint8 option) external {
(bool pre, , ) = POLICY.enforced(address(this), msg.sender);

if (!pre) revert NotRegistered();
if (option >= 2) revert InvalidOption();

bytes memory evidence = abi.encode(option);
POLICY.enforce(msg.sender, evidence, Check.MAIN);

unchecked {
voteCounts[option]++;
}

emit Voted(msg.sender, option);
}

/**
* @notice Claim NFT reward after voting.
* @dev Validates voting participation and transfers reward NFT.
* @param rewardId NFT ID to be claimed as reward.
*/
function reward(uint256 rewardId) external {
(bool pre, uint8 main, bool post) = POLICY.enforced(address(this), msg.sender);

if (!pre) revert NotRegistered();
if (main == 0) revert NotVoted();
if (post) revert AlreadyClaimed();

bytes memory evidence = abi.encode(rewardId);
POLICY.enforce(msg.sender, evidence, Check.POST);

emit RewardClaimed(msg.sender, rewardId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Check} from "../../interfaces/IAdvancedChecker.sol";

// This contract is a harness for testing the AdvancedERC721Checker contract.
// Deploy this contract and call its methods to test the internal methods of AdvancedERC721Checker.
contract AdvancedERC721CheckerHarness is AdvancedERC721Checker {
constructor(
IERC721 _nft,
uint256 _minBalance,
uint256 _minTokenId,
uint256 _maxTokenId,
bool _skipPre,
bool _skipPost,
bool _allowMultipleMain
) AdvancedERC721Checker(_nft, _minBalance, _minTokenId, _maxTokenId, _skipPre, _skipPost, _allowMultipleMain) {}

/// @notice Exposes the internal `_check` method for testing purposes.
/// @param subject The address to be checked.
/// @param evidence The data associated with the check.
/// @param checkType The type of check to perform (PRE, MAIN, POST).
function exposed__check(address subject, bytes calldata evidence, Check checkType) public view returns (bool) {
return _check(subject, evidence, checkType);
}

/// @notice Exposes the internal `_checkPre` method for testing purposes.
/// @param subject The address to be checked.
/// @param evidence The data associated with the check.
function exposed__checkPre(address subject, bytes calldata evidence) public view returns (bool) {
return _checkPre(subject, evidence);
}

/// @notice Exposes the internal `_checkMain` method for testing purposes.
/// @param subject The address to be checked.
/// @param evidence The data associated with the check.
function exposed__checkMain(address subject, bytes calldata evidence) public view returns (bool) {
return _checkMain(subject, evidence);
}

/// @notice Exposes the internal `_checkPost` method for testing purposes.
/// @param subject The address to be checked.
/// @param evidence The data associated with the check.
function exposed__checkPost(address subject, bytes calldata evidence) public view returns (bool) {
return _checkPost(subject, evidence);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

// This contract is a harness for testing the AdvancedERC721Policy contract.
// Deploy this contract and call its methods to test the internal methods of AdvancedERC721Policy.
contract AdvancedERC721PolicyHarness is AdvancedERC721Policy {
constructor(AdvancedERC721Checker _checker) AdvancedERC721Policy(_checker) {}

/// @notice Exposes the internal `_enforce` method for testing purposes.
/// @param subject The address of those who have successfully enforced the check.
/// @param evidence Additional data required for the check (e.g., encoded token identifier).
/// @param checkType The type of the check to be enforced for the subject with the given data.
function exposed__enforce(address subject, bytes calldata evidence, Check checkType) internal onlyTarget {
_enforce(subject, evidence, checkType);
}
}
Loading

0 comments on commit ab8f3fa

Please sign in to comment.