From 91ec2c3ca48d24908d8a6f2e5e768a1cc063c49e Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 17 Jan 2025 15:37:48 +0100 Subject: [PATCH] refactor(contracts): core contracts optimization, reflect on tests --- .../contracts/src/AdvancedChecker.sol | 2 +- packages/contracts/contracts/src/Checker.sol | 14 +- .../contracts/src/interfaces/IChecker.sol | 6 + .../contracts/src/test/Advanced.t.sol | 400 +++++++++--------- .../contracts/contracts/src/test/Base.t.sol | 19 + .../test/advanced/AdvancedERC721Checker.sol | 65 +-- .../src/test/advanced/AdvancedVoting.sol | 98 ++--- .../src/test/base/BaseERC721Checker.sol | 2 +- .../wrappers/AdvancedERC721CheckerHarness.sol | 8 + .../wrappers/BaseERC721CheckerHarness.sol | 8 + packages/contracts/test/Advanced.test.ts | 392 ++++++++++------- packages/contracts/test/Base.test.ts | 27 ++ 12 files changed, 590 insertions(+), 451 deletions(-) diff --git a/packages/contracts/contracts/src/AdvancedChecker.sol b/packages/contracts/contracts/src/AdvancedChecker.sol index 922b245..b850ad7 100644 --- a/packages/contracts/contracts/src/AdvancedChecker.sol +++ b/packages/contracts/contracts/src/AdvancedChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IAdvancedChecker, Check, CheckStatus} from "./interfaces/IAdvancedChecker.sol"; +import {IAdvancedChecker, Check} from "./interfaces/IAdvancedChecker.sol"; import {Checker} from "./Checker.sol"; /// @title AdvancedChecker. diff --git a/packages/contracts/contracts/src/Checker.sol b/packages/contracts/contracts/src/Checker.sol index d149fd6..83cc4a6 100644 --- a/packages/contracts/contracts/src/Checker.sol +++ b/packages/contracts/contracts/src/Checker.sol @@ -20,17 +20,19 @@ abstract contract Checker is IChecker { verifiers = _verifiers; } - /// @notice Retrieves the list of third-party verifiers' addresses. - /// @return Array of addresses for the necessary verification contracts. - function getVerifiers() internal view returns (address[] memory) { - return 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 Retrieves the verifier address at a specific 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) { + function _getVerifierAtIndex(uint256 index) internal view returns (address) { if (index >= verifiers.length) revert VerifierNotFound(); return verifiers[index]; diff --git a/packages/contracts/contracts/src/interfaces/IChecker.sol b/packages/contracts/contracts/src/interfaces/IChecker.sol index 3b1b847..f8898a9 100644 --- a/packages/contracts/contracts/src/interfaces/IChecker.sol +++ b/packages/contracts/contracts/src/interfaces/IChecker.sol @@ -6,4 +6,10 @@ pragma solidity ^0.8.20; 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/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index 78902a0..f84fb3f 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -3,11 +3,13 @@ pragma solidity ^0.8.20; import {Test} 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 {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol"; import {AdvancedVoting} from "./advanced/AdvancedVoting.sol"; import {AdvancedERC721CheckerHarness} from "./wrappers/AdvancedERC721CheckerHarness.sol"; import {AdvancedERC721PolicyHarness} from "./wrappers/AdvancedERC721PolicyHarness.sol"; +import {IChecker} from "../interfaces/IChecker.sol"; import {IPolicy} from "../interfaces/IPolicy.sol"; import {IAdvancedPolicy} from "../interfaces/IAdvancedPolicy.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; @@ -15,28 +17,39 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Check} from "../interfaces/IAdvancedChecker.sol"; contract AdvancedChecker is Test { - NFT internal nft; - AdvancedERC721Checker internal checker; - AdvancedERC721CheckerHarness internal checkerHarness; + NFT internal signupNft; + NFT internal rewardNft; + BaseERC721Checker internal baseChecker; + AdvancedERC721Checker internal advancedChecker; + AdvancedERC721CheckerHarness internal advancedCheckerHarness; 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; + 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); - nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + signupNft = new NFT(); + rewardNft = new NFT(); - checker = new AdvancedERC721Checker(verifiers, 1, 0, 10); - checkerHarness = new AdvancedERC721CheckerHarness(verifiers, 1, 0, 10); + 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); + advancedCheckerHarness = new AdvancedERC721CheckerHarness(advancedVerifiers, 1, 0, 10); evidence[0] = abi.encode(0); wrongEvidence[0] = abi.encode(1); @@ -44,11 +57,29 @@ contract AdvancedChecker is Test { vm.stopPrank(); } + function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { + assertEq(advancedChecker.getVerifierAtIndex(0), address(signupNft)); + } + + function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { + vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + advancedChecker.getVerifierAtIndex(5); + } + + 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_checkPre_whenTokenDoesNotExist_reverts() public { vm.startPrank(target); vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - checker.check(subject, evidence, Check.PRE); + advancedChecker.check(subject, evidence, Check.PRE); vm.stopPrank(); } @@ -56,9 +87,9 @@ contract AdvancedChecker is Test { function test_checkPre_whenCallerNotOwner_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checker.check(notOwner, evidence, Check.PRE)); + assert(!advancedChecker.check(notOwner, evidence, Check.PRE)); vm.stopPrank(); } @@ -66,9 +97,9 @@ contract AdvancedChecker is Test { function test_checkPre_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checker.check(subject, evidence, Check.PRE)); + assert(advancedChecker.check(subject, evidence, Check.PRE)); vm.stopPrank(); } @@ -76,9 +107,9 @@ contract AdvancedChecker is Test { function test_checkMain_whenCallerHasNoTokens_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checker.check(notOwner, evidence, Check.MAIN)); + assert(!advancedChecker.check(notOwner, evidence, Check.MAIN)); vm.stopPrank(); } @@ -86,28 +117,19 @@ contract AdvancedChecker is Test { function test_checkMain_whenCallerHasTokens_succeeds() public { vm.startPrank(target); - nft.mint(subject); - - assert(checker.check(subject, evidence, Check.MAIN)); + signupNft.mint(subject); - vm.stopPrank(); - } - - function test_checkPost_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - checker.check(subject, wrongEvidence, Check.POST); + assert(advancedChecker.check(subject, evidence, Check.MAIN)); vm.stopPrank(); } - function test_checkPost_whenCallerNotOwner_returnsFalse() public { + function test_checkPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + rewardNft.mint(subject); - assert(!checker.check(notOwner, evidence, Check.POST)); + assert(!advancedChecker.check(subject, evidence, Check.POST)); vm.stopPrank(); } @@ -115,9 +137,9 @@ contract AdvancedChecker is Test { function test_checkPost_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checker.check(subject, evidence, Check.POST)); + assert(advancedChecker.check(subject, evidence, Check.POST)); vm.stopPrank(); } @@ -126,7 +148,7 @@ contract AdvancedChecker is Test { vm.startPrank(target); vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - checkerHarness.exposed__check(subject, evidence, Check.PRE); + advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE); vm.stopPrank(); } @@ -134,9 +156,9 @@ contract AdvancedChecker is Test { function test_checkerPre_whenCallerNotOwner_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checkerHarness.exposed__check(notOwner, evidence, Check.PRE)); + assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.PRE)); vm.stopPrank(); } @@ -144,9 +166,9 @@ contract AdvancedChecker is Test { function test_checkerPre_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checkerHarness.exposed__check(subject, evidence, Check.PRE)); + assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE)); vm.stopPrank(); } @@ -154,9 +176,9 @@ contract AdvancedChecker is Test { function test_checkerMain_whenCallerHasNoTokens_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checkerHarness.exposed__check(notOwner, evidence, Check.MAIN)); + assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.MAIN)); vm.stopPrank(); } @@ -164,28 +186,19 @@ contract AdvancedChecker is Test { function test_checkerMain_whenCallerHasTokens_succeeds() public { vm.startPrank(target); - nft.mint(subject); - - assert(checkerHarness.exposed__check(subject, evidence, Check.MAIN)); - - vm.stopPrank(); - } - - function test_checkerPost_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + signupNft.mint(subject); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - checkerHarness.exposed__check(subject, wrongEvidence, Check.POST); + assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.MAIN)); vm.stopPrank(); } - function test_checkerPost_whenCallerNotOwner_returnsFalse() public { + function test_checkerPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + rewardNft.mint(subject); - assert(!checkerHarness.exposed__check(notOwner, evidence, Check.POST)); + assert(!advancedCheckerHarness.check(subject, evidence, Check.POST)); vm.stopPrank(); } @@ -193,9 +206,9 @@ contract AdvancedChecker is Test { function test_checkerPost_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checkerHarness.exposed__check(subject, evidence, Check.POST)); + assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.POST)); vm.stopPrank(); } @@ -204,7 +217,7 @@ contract AdvancedChecker is Test { vm.startPrank(target); vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - checkerHarness.exposed__checkPre(subject, wrongEvidence); + advancedCheckerHarness.exposed__checkPre(subject, wrongEvidence); vm.stopPrank(); } @@ -212,9 +225,9 @@ contract AdvancedChecker is Test { function test_internalPre_whenCallerNotOwner_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checkerHarness.exposed__checkPre(notOwner, evidence)); + assert(!advancedCheckerHarness.exposed__checkPre(notOwner, evidence)); vm.stopPrank(); } @@ -222,9 +235,9 @@ contract AdvancedChecker is Test { function test_internalPre_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checkerHarness.exposed__checkPre(subject, evidence)); + assert(advancedCheckerHarness.exposed__checkPre(subject, evidence)); vm.stopPrank(); } @@ -232,9 +245,9 @@ contract AdvancedChecker is Test { function test_internalMain_whenCallerHasNoTokens_returnsFalse() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(!checkerHarness.exposed__checkMain(notOwner, evidence)); + assert(!advancedCheckerHarness.exposed__checkMain(notOwner, evidence)); vm.stopPrank(); } @@ -242,28 +255,19 @@ contract AdvancedChecker is Test { function test_internalMain_whenCallerHasTokens_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checkerHarness.exposed__checkMain(subject, evidence)); + assert(advancedCheckerHarness.exposed__checkMain(subject, evidence)); vm.stopPrank(); } - function test_internalPost_whenTokenDoesNotExist_reverts() public { + function test_internalPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - checkerHarness.exposed__checkPost(subject, wrongEvidence); + rewardNft.mint(subject); - vm.stopPrank(); - } - - function test_internalPost_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); - - nft.mint(subject); - - assert(!checkerHarness.exposed__checkPost(notOwner, evidence)); + assert(!advancedCheckerHarness.exposed__checkPost(subject, evidence)); vm.stopPrank(); } @@ -271,9 +275,9 @@ contract AdvancedChecker is Test { function test_internalPost_whenValid_succeeds() public { vm.startPrank(target); - nft.mint(subject); + signupNft.mint(subject); - assert(checkerHarness.exposed__checkPost(subject, evidence)); + assert(advancedCheckerHarness.exposed__checkPost(subject, evidence)); vm.stopPrank(); } @@ -283,9 +287,11 @@ contract AdvancedPolicy is Test { event TargetSet(address indexed target); event Enforced(address indexed subject, address indexed target, bytes[] evidence, Check checkType); - NFT internal nft; - AdvancedERC721Checker internal checker; - AdvancedERC721Checker internal checkerSkipped; + NFT internal signupNft; + NFT internal rewardNft; + BaseERC721Checker internal baseChecker; + AdvancedERC721Checker internal advancedChecker; + AdvancedERC721Checker internal advancedCheckerSkipped; AdvancedERC721Policy internal policy; AdvancedERC721Policy internal policySkipped; AdvancedERC721PolicyHarness internal policyHarness; @@ -296,23 +302,32 @@ contract AdvancedPolicy is Test { address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal verifiers; + 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); - nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + 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); - checker = new AdvancedERC721Checker(verifiers, 1, 0, 10); - checkerSkipped = new AdvancedERC721Checker(verifiers, 1, 0, 10); - policy = new AdvancedERC721Policy(checker, false, false, true); - policyHarness = new AdvancedERC721PolicyHarness(checker, false, false, true); - policySkipped = new AdvancedERC721Policy(checkerSkipped, true, true, false); - policyHarnessSkipped = new AdvancedERC721PolicyHarness(checkerSkipped, true, true, false); + 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); evidence[0] = abi.encode(0); wrongEvidence[0] = abi.encode(1); @@ -398,7 +413,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policySkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -414,7 +429,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -430,7 +445,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -448,7 +463,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -496,7 +511,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -512,7 +527,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -532,7 +547,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -557,7 +572,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policySkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -575,7 +590,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -603,29 +618,11 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); - - policy.setTarget(target); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - policy.enforce(subject, wrongEvidence, Check.POST); - - vm.stopPrank(); - } - function test_enforcePost_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -643,7 +640,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -652,8 +649,10 @@ contract AdvancedPolicy is Test { policy.enforce(subject, evidence, Check.PRE); policy.enforce(subject, evidence, Check.MAIN); + rewardNft.mint(subject); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policy.enforce(notOwner, evidence, Check.POST); + policy.enforce(subject, evidence, Check.POST); vm.stopPrank(); } @@ -662,7 +661,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -683,7 +682,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policy.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -733,7 +732,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarnessSkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -749,7 +748,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -765,7 +764,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -783,7 +782,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -831,7 +830,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -847,7 +846,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -867,7 +866,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -892,7 +891,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarnessSkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -910,7 +909,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -938,29 +937,11 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePostInternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); - - policyHarness.setTarget(target); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(target); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - policyHarness.exposed__enforce(subject, wrongEvidence, Check.POST); - - vm.stopPrank(); - } - function test_enforcePostInternal_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policyHarnessSkipped.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -978,7 +959,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -987,8 +968,10 @@ contract AdvancedPolicy is Test { 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(notOwner, evidence, Check.POST); + policyHarness.exposed__enforce(subject, evidence, Check.POST); vm.stopPrank(); } @@ -997,7 +980,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1018,7 +1001,7 @@ contract AdvancedPolicy is Test { vm.startPrank(deployer); policyHarness.setTarget(target); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1038,27 +1021,39 @@ contract AdvancedPolicy is Test { contract Voting is Test { event Registered(address voter); event Voted(address voter, uint8 option); - event RewardClaimed(address voter, uint256 rewardId); + event Eligible(address voter); - NFT internal nft; - AdvancedERC721Checker internal checker; + NFT internal signupNft; + NFT internal rewardNft; + BaseERC721Checker internal baseChecker; + AdvancedERC721Checker internal advancedChecker; AdvancedERC721Policy internal policy; AdvancedVoting internal voting; - address[] internal verifiers; 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); - nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + signupNft = new NFT(); + rewardNft = new NFT(); + + baseVerifiers = new address[](1); + baseVerifiers[0] = address(signupNft); + baseChecker = new BaseERC721Checker(baseVerifiers); - checker = new AdvancedERC721Checker(verifiers, 1, 0, 10); - policy = new AdvancedERC721Policy(checker, false, false, true); + 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(); @@ -1068,7 +1063,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(deployer); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1084,7 +1079,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1100,7 +1095,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1116,7 +1111,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1134,7 +1129,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1152,7 +1147,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1168,7 +1163,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1185,7 +1180,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1204,7 +1199,7 @@ contract Voting is Test { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1220,31 +1215,12 @@ contract Voting is Test { vm.stopPrank(); } - function test_reward_whenTokenDoesNotExist_reverts() public { + function test_eligible_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); - - vm.stopPrank(); - - vm.startPrank(subject); - - voting.register(0); - voting.vote(0); - - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - voting.reward(1); - - vm.stopPrank(); - } - - function test_reward_whenCheckFails_reverts() public { - vm.startPrank(deployer); - - policy.setTarget(address(voting)); - nft.mint(subject); - nft.mint(notOwner); + signupNft.mint(subject); + signupNft.mint(notOwner); vm.stopPrank(); @@ -1258,33 +1234,35 @@ contract Voting is Test { voting.register(0); voting.vote(0); + rewardNft.mint(subject); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - voting.reward(1); + voting.eligible(); vm.stopPrank(); } - function test_reward_whenNotRegistered_reverts() public { + function test_eligible_whenNotRegistered_reverts() public { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); vm.startPrank(subject); vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); - voting.reward(0); + voting.eligible(); vm.stopPrank(); } - function test_reward_whenNotVoted_reverts() public { + function test_eligible_whenNotVoted_reverts() public { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1292,16 +1270,16 @@ contract Voting is Test { voting.register(0); vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotVoted.selector)); - voting.reward(0); + voting.eligible(); vm.stopPrank(); } - function test_reward_whenValid_succeeds() public { + function test_eligible_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1311,18 +1289,18 @@ contract Voting is Test { voting.vote(0); vm.expectEmit(true, true, true, true); - emit RewardClaimed(subject, 0); + emit Eligible(subject); - voting.reward(0); + voting.eligible(); vm.stopPrank(); } - function test_reward_whenAlreadyClaimed_reverts() public { + function test_eligible_whenAlreadyEligible_reverts() public { vm.startPrank(deployer); policy.setTarget(address(voting)); - nft.mint(subject); + signupNft.mint(subject); vm.stopPrank(); @@ -1330,10 +1308,10 @@ contract Voting is Test { voting.register(0); voting.vote(0); - voting.reward(0); + voting.eligible(); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.AlreadyClaimed.selector)); - voting.reward(0); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.AlreadyEligible.selector)); + voting.eligible(); vm.stopPrank(); } diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index eef8978..60f9abc 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -9,6 +9,7 @@ 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 {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -40,6 +41,24 @@ contract BaseChecker is Test { 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_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { + assertEq(checkerHarness.exposed__getVerifierAtIndex(0), address(nft)); + } + + function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { + vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + checkerHarness.exposed__getVerifierAtIndex(1); + } + function test_checker_whenTokenDoesNotExist_reverts() public { vm.startPrank(target); diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index 309959f..644b944 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -2,61 +2,78 @@ pragma solidity ^0.8.20; import {AdvancedChecker} from "../../AdvancedChecker.sol"; +import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title AdvancedERC721Checker. -/// @notice Multi-phase ERC721 token validation. -/// @dev Implements pre, main, and post checks for NFTs. +/// @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. contract AdvancedERC721Checker is AdvancedChecker { - /// @notice Contract references and thresholds. - IERC721 public immutable NFT; + /// @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 Validation thresholds. uint256 public immutable MIN_BALANCE; uint256 public immutable MIN_TOKEN_ID; uint256 public immutable MAX_TOKEN_ID; - /// @notice Initializes checker with validation parameters. - /// @param _verifiers Array of addresses for existing verification contracts. - /// @param _minBalance Required token balance. - /// @param _minTokenId Minimum valid token ID. - /// @param _maxTokenId Maximum valid token ID. + /// @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) { - NFT = IERC721(getVerifierAtIndex(0)); + 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 Validates basic token ownership. + /// @notice Pre-check: Validates initial NFT ownership. + /// @dev Delegates basic ownership check to BaseERC721Checker. /// @param subject Address to validate. - /// @param evidence Encoded tokenId. - /// @return Token ownership status. + /// @param evidence Array containing encoded tokenId. + /// @return Validation status from base checker. function _checkPre(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPre(subject, evidence); - uint256 tokenId = abi.decode(evidence[0], (uint256)); - return NFT.ownerOf(tokenId) == subject; + return BASE_ERC721_CHECKER.check(subject, evidence); } - /// @notice Validates minimum token balance. + /// @notice Main-check: Validates token balance requirements. + /// @dev Ensures subject has exactly MIN_BALANCE tokens. /// @param subject Address to validate. - /// @param evidence Unused parameter. - /// @return Balance threshold status. + /// @param evidence Not used in balance check. + /// @return True if balance meets requirements. function _checkMain(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkMain(subject, evidence); - return NFT.balanceOf(subject) >= MIN_BALANCE; + return SIGNUP_NFT.balanceOf(subject) >= MIN_BALANCE && SIGNUP_NFT.balanceOf(subject) <= MIN_BALANCE; } - /// @notice Validates token ID range ownership. + /// @notice Post-check: Validates reward eligibility. + /// @dev Ensures subject doesn't already have reward tokens. /// @param subject Address to validate. - /// @param evidence Encoded tokenId. - /// @return Token range validation status. + /// @param evidence Not used in reward check. + /// @return True if subject eligible for rewards. function _checkPost(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPost(subject, evidence); - uint256 tokenId = abi.decode(evidence[0], (uint256)); - return tokenId >= MIN_TOKEN_ID && tokenId <= MAX_TOKEN_ID && NFT.ownerOf(tokenId) == subject; + return REWARD_NFT.balanceOf(subject) == 0; } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol index c182f0c..8d69c78 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol @@ -5,73 +5,77 @@ import {AdvancedPolicy} from "../../AdvancedPolicy.sol"; import {Check} from "../../interfaces/IAdvancedPolicy.sol"; /// @title AdvancedVoting. -/// @notice Multi-phase voting system with NFT validation and rewards. -/// @dev Implements a three-phase voting process: -/// 1. Registration (PRE): Validates initial NFT ownership. -/// 2. Voting (MAIN): Validates voting power and records vote. -/// 3. Rewards (POST): Validates and distributes NFT rewards. +/// @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. contract AdvancedVoting { - /// @notice Events for tracking system state changes. + /// @notice Emitted on successful phase completion. + /// @param voter Address that completed the phase. event Registered(address voter); + /// @param option Selected voting option (0 or 1). event Voted(address voter, uint8 option); - event RewardClaimed(address voter, uint256 rewardId); - - /// @notice System error conditions. - error NotRegistered(); - error NotVoted(); - error AlreadyClaimed(); - error InvalidOption(); - error NotOwnerOfReward(); - - /// @notice Policy contract for validation checks. + /// @param voter Address that 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 Policy contract managing multi-phase validation. + /// @dev Handles all NFT-based checks through aggregated verifiers. AdvancedPolicy public immutable POLICY; - /// @notice Tracks total votes per option. - /// @dev Maps option ID => vote count. + /// @notice Vote tracking per option. + /// @dev Maps option ID (0 or 1) to total votes received. mapping(uint8 => uint256) public voteCounts; - /// @notice Sets up voting system with policy contract. - /// @param _policy Contract handling validation logic. + /// @notice Initializes voting system. + /// @param _policy Advanced policy contract with configured verifiers. constructor(AdvancedPolicy _policy) { POLICY = _policy; } - /// @notice First phase - Register voter with NFT ownership proof. - /// @dev Enforces PRE check through policy contract. - /// @param tokenId NFT used for registration. - /// @custom:requirements Caller must own the NFT with tokenId. - /// @custom:emits Registered on successful registration. + /// @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. function register(uint256 tokenId) external { - // Encode token ID for policy verification. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(tokenId); - // Verify NFT ownership through policy's PRE check. POLICY.enforce(msg.sender, _evidence, Check.PRE); emit Registered(msg.sender); } - /// @notice Second phase - Cast vote after registration. - /// @dev Enforces MAIN check and updates vote counts. - /// @param option Vote choice (0 or 1). + /// @notice Voting phase handler. + /// @dev Validates voting power and records vote choice. + /// @param option Binary choice (0 or 1). /// @custom:requirements - /// - Caller must be registered (passed PRE check). + /// - Registration must be completed. /// - Option must be valid (0 or 1). - /// @custom:emits Voted on successful vote cast. + /// - Token balance must meet requirements. + /// @custom:emits Voted when vote is recorded. function vote(uint8 option) external { - // Check registration status (PRE check completion). (bool pre, , ) = POLICY.enforced(address(this), msg.sender); - if (!pre) revert NotRegistered(); if (option >= 2) revert InvalidOption(); - // Verify voting power through policy's MAIN check. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(option); + POLICY.enforce(msg.sender, _evidence, Check.MAIN); - // Increment vote count safely. unchecked { voteCounts[option]++; } @@ -79,27 +83,23 @@ contract AdvancedVoting { emit Voted(msg.sender, option); } - /// @notice Final phase - Claim NFT reward after voting. - /// @dev Enforces POST check for reward distribution. - /// @param rewardId Identifier of NFT reward to claim. + /// @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 have claimed before (no POST check). - /// @custom:emits RewardClaimed on successful claim. - function reward(uint256 rewardId) external { - // Verify completion of previous phases. + /// - Caller must not be already verified (no POST check). + /// - Caller must meet eligibility criteria (no existing benefits). + /// @custom:emits Eligible when verification succeeds. + function eligible() external { (bool pre, uint8 main, bool post) = POLICY.enforced(address(this), msg.sender); if (!pre) revert NotRegistered(); if (main == 0) revert NotVoted(); - if (post) revert AlreadyClaimed(); + if (post) revert AlreadyEligible(); - // Verify reward eligibility through policy's POST check. - bytes[] memory _evidence = new bytes[](1); - _evidence[0] = abi.encode(rewardId); - POLICY.enforce(msg.sender, _evidence, Check.POST); + POLICY.enforce(msg.sender, new bytes[](1), Check.POST); - emit RewardClaimed(msg.sender, rewardId); + 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 b8cc57d..759fc02 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -14,7 +14,7 @@ contract BaseERC721Checker is BaseChecker { /// @notice Initializes with ERC721 contract. /// @param _verifiers Array of addresses for existing verification contracts. constructor(address[] memory _verifiers) BaseChecker(_verifiers) { - NFT = IERC721(getVerifierAtIndex(0)); + NFT = IERC721(_getVerifierAtIndex(0)); } /// @notice Validates token ownership. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol index 1d0511d..a48995e 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol @@ -52,4 +52,12 @@ contract AdvancedERC721CheckerHarness is AdvancedERC721Checker { 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/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol index d97448d..f5458bb 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol @@ -17,4 +17,12 @@ contract BaseERC721CheckerHarness is BaseERC721Checker { 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/test/Advanced.test.ts b/packages/contracts/test/Advanced.test.ts index 55c816b..ce76a40 100644 --- a/packages/contracts/test/Advanced.test.ts +++ b/packages/contracts/test/Advanced.test.ts @@ -3,6 +3,8 @@ import { ethers } from "hardhat" import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" import { + BaseERC721Checker, + BaseERC721Checker__factory, AdvancedERC721Checker, AdvancedERC721Checker__factory, AdvancedERC721CheckerHarness, @@ -26,36 +28,47 @@ describe("Advanced", () => { 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 nft: NFT = await NFTFactory.deploy() - - const checker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await nft.getAddress()], + 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()], 1, 0, 10 ) - const checkerHarness: AdvancedERC721CheckerHarness = await AdvancedERC721CheckerHarnessFactory.connect( - deployer - ).deploy([await nft.getAddress()], 1, 0, 10) + const advancedCheckerHarness: AdvancedERC721CheckerHarness = + await AdvancedERC721CheckerHarnessFactory.connect(deployer).deploy( + [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + 1, + 0, + 10 + ) // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) + await signupNft.connect(deployer).mint(subjectAddress) // encoded token ids. const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { - nft, - checker, + signupNft, + rewardNft, + baseChecker, + advancedChecker, + advancedCheckerHarness, deployer, - checkerHarness, target, subject, subjectAddress, @@ -67,151 +80,181 @@ describe("Advanced", () => { describe("constructor", () => { it("deploys correctly", async () => { - const { checker } = await loadFixture(deployAdvancedCheckerFixture) + const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) - expect(checker).to.not.eq(undefined) + expect(advancedChecker).to.not.eq(undefined) }) }) describe("check", () => { describe("pre check", () => { it("reverts when evidence invalid", async () => { - const { nft, checker, target, subjectAddress, invalidNFTId } = + const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) await expect( - checker.connect(target).check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") + advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 0) + ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") }) it("returns false when not owner", async () => { - const { checker, target, notOwnerAddress, validNFTId } = + const { advancedChecker, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) - expect(await checker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal(false) + expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal( + false + ) }) it("succeeds when valid", async () => { - const { checker, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) - expect(await checker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal(true) + expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal( + true + ) }) }) describe("main check", () => { it("returns false when balance insufficient", async () => { - const { checker, target, notOwnerAddress, validNFTId } = + const { advancedChecker, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) - expect(await checker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal(false) + expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal( + false + ) }) it("succeeds when balance sufficient", async () => { - const { checker, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) - expect(await checker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal(true) + expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal( + true + ) }) }) describe("post check", () => { - it("reverts when evidence invalid", async () => { - const { nft, checker, target, subjectAddress, invalidNFTId } = + it("reverts when already rewarded", async () => { + const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) - await expect( - checker.connect(target).check(subjectAddress, [invalidNFTId], 2) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) + await rewardNft.mint(subjectAddress) - it("returns false when not in range", async () => { - const { checker, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await checker.connect(target).check(notOwnerAddress, [validNFTId], 2)).to.be.equal(false) + expect(await advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 2)).to.be.equal( + false + ) }) it("succeeds when in valid range", async () => { - const { checker, target, subjectAddress, validNFTId } = + const { advancedChecker, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) - expect(await checker.connect(target).check(subjectAddress, [validNFTId], 2)).to.be.equal(true) + 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( + advancedChecker, + "VerifierNotFound" + ) + }) + }) + + 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() + ) + }) + + it("reverts when index out of bounds", async () => { + const { advancedCheckerHarness } = await loadFixture(deployAdvancedCheckerFixture) + await expect(advancedCheckerHarness.exposed__getVerifierAtIndex(5)).to.be.revertedWithCustomError( + advancedCheckerHarness, + "VerifierNotFound" + ) + }) + }) + describe("internal checks", () => { describe("pre check", () => { it("reverts when evidence invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = + const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) await expect( - checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") + advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) + ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") }) it("returns false when not owner", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = + const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) + await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) ).to.be.equal(false) }) it("succeeds when valid", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) + await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) ).to.be.equal(true) }) }) describe("main check", () => { it("returns false when balance insufficient", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = + const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) + await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) ).to.be.equal(false) }) it("succeeds when balance sufficient", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) + await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) ).to.be.equal(true) }) }) describe("post check", () => { it("reverts when evidence invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = + const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) - await expect( - checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 2) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) - - it("returns false when not in range", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) + await rewardNft.mint(subjectAddress) expect( - await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 2) + await advancedCheckerHarness.connect(target).check(subjectAddress, [invalidNFTId], 2) ).to.be.equal(false) }) it("succeeds when in valid range", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) + await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) ).to.be.equal(true) }) }) @@ -219,78 +262,71 @@ describe("Advanced", () => { describe("internal checkPre", () => { it("reverts when evidence invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = + const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) await expect( - checkerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") + advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) + ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") }) it("returns false when not owner", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = + const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) ).to.be.equal(false) }) it("succeeds when valid", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) ).to.be.equal(true) }) }) describe("internal checkMain", () => { it("returns false when balance insufficient", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = + const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) ).to.be.equal(false) }) it("succeeds when balance sufficient", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) ).to.be.equal(true) }) }) describe("internal checkPost", () => { it("reverts when evidence invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = + const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = await loadFixture(deployAdvancedCheckerFixture) - await expect( - checkerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) - - it("returns false when not in range", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) + await rewardNft.mint(subjectAddress) expect( - await checkerHarness.connect(target).exposed__checkPost(notOwnerAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) ).to.be.equal(false) }) it("succeeds when in valid range", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = + const { advancedCheckerHarness, target, subjectAddress, validNFTId } = await loadFixture(deployAdvancedCheckerFixture) expect( - await checkerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) + await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) ).to.be.equal(true) }) }) @@ -303,6 +339,8 @@ describe("Advanced", () => { 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 = @@ -310,51 +348,68 @@ describe("Advanced", () => { const AdvancedERC721PolicyHarnessFactory: AdvancedERC721PolicyHarness__factory = await ethers.getContractFactory("AdvancedERC721PolicyHarness") - const nft: NFT = await NFTFactory.deploy() - const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - - const checker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await nft.getAddress()], + const signupNft: NFT = await NFTFactory.deploy() + const rewardNft: NFT = await NFTFactory.deploy() + const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await signupNft.getAddress() + ) + const rewardIERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await rewardNft.getAddress() + ) + 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()], 1, 0, 10 ) - const checkerSkippedPrePostNoMultMain: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect( - deployer - ).deploy([await nft.getAddress()], 1, 0, 10) + const advancedCheckerSkippedPrePostNoMultMain: AdvancedERC721Checker = + await AdvancedERC721CheckerFactory.connect(deployer).deploy( + [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], + 1, + 0, + 10 + ) const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress(), + await advancedChecker.getAddress(), false, false, true ) const policySkipped: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await checkerSkippedPrePostNoMultMain.getAddress(), + await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false ) const policyHarness: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( deployer - ).deploy(await checker.getAddress(), false, false, true) + ).deploy(await advancedChecker.getAddress(), false, false, true) const policyHarnessSkipped: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( deployer - ).deploy(await checkerSkippedPrePostNoMultMain.getAddress(), true, true, false) + ).deploy(await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false) // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) + await signupNft.connect(deployer).mint(subjectAddress) // encoded token ids. const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) return { - iERC721Errors, + signupIERC721Errors, + rewardIERC721Errors, AdvancedERC721PolicyFactory, - nft, - checker, + signupNft, + rewardNft, + advancedChecker, + advancedCheckerSkippedPrePostNoMultMain, policyHarness, policyHarnessSkipped, policy, @@ -446,14 +501,14 @@ describe("Advanced", () => { }) it("reverts when evidence invalid", async () => { - const { iERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = + const { rewardIERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = await loadFixture(deployAdvancedPolicyFixture) await policy.setTarget(await target.getAddress()) await expect( policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 0) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") }) it("reverts when pre-check skipped", async () => { @@ -647,17 +702,19 @@ describe("Advanced", () => { ).to.be.revertedWithCustomError(policy, "TargetOnly") }) - it("reverts when evidence invalid", async () => { - const { iERC721Errors, policy, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = + 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) + await rewardNft.mint(subjectAddress) + await expect( policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") }) it("reverts when post-check skipped", async () => { @@ -745,14 +802,14 @@ describe("Advanced", () => { }) it("reverts when evidence invalid", async () => { - const { iERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = + 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(iERC721Errors, "ERC721NonexistentToken") + ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") }) it("reverts when pre-check skipped", async () => { @@ -950,22 +1007,18 @@ describe("Advanced", () => { }) it("reverts when evidence invalid", async () => { - const { - iERC721Errors, - policyHarness, - target, - subjectAddress, - validEncodedNFTId, - invalidEncodedNFTId - } = await loadFixture(deployAdvancedPolicyFixture) + 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).exposed__enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + policyHarness.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) + ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") }) it("reverts when post-check skipped", async () => { @@ -1047,24 +1100,36 @@ describe("Advanced", () => { 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 nft: NFT = await NFTFactory.deploy() - const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - - const checker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await nft.getAddress()], + const signupNft: NFT = await NFTFactory.deploy() + const rewardNft: NFT = await NFTFactory.deploy() + const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await signupNft.getAddress() + ) + const rewardIERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await rewardNft.getAddress() + ) + 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()], 1, 0, 10 ) const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress(), + await advancedChecker.getAddress(), false, false, true @@ -1075,7 +1140,7 @@ describe("Advanced", () => { ) // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) + await signupNft.connect(deployer).mint(subjectAddress) // encoded token ids. const validNFTId = 0 @@ -1084,9 +1149,13 @@ describe("Advanced", () => { const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) return { - iERC721Errors, + signupIERC721Errors, + rewardIERC721Errors, AdvancedVotingFactory, - nft, + AdvancedERC721PolicyFactory, + signupNft, + rewardNft, + advancedChecker, voting, policy, subject, @@ -1122,13 +1191,13 @@ describe("Advanced", () => { }) it("reverts when evidence invalid", async () => { - const { iERC721Errors, voting, policy, subject, invalidNFTId } = + const { signupIERC721Errors, voting, policy, subject, invalidNFTId } = await loadFixture(deployAdvancedVotingFixture) await policy.setTarget(await voting.getAddress()) await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( - iERC721Errors, + signupIERC721Errors, "ERC721NonexistentToken" ) }) @@ -1262,7 +1331,7 @@ describe("Advanced", () => { }) }) - describe("reward", () => { + describe("eligibility", () => { it("reverts when caller not target", async () => { const { voting, policy, subject, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) @@ -1274,46 +1343,47 @@ describe("Advanced", () => { ) }) - it("reverts when evidence invalid", async () => { - const { iERC721Errors, voting, policy, subject, validNFTId, invalidNFTId } = + it("reverts when already owns reward token", async () => { + const { rewardNft, voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) await policy.setTarget(await voting.getAddress()) await voting.connect(subject).register(validNFTId) await voting.connect(subject).vote(0) - await expect(voting.connect(subject).reward(invalidNFTId)).to.be.revertedWithCustomError( - iERC721Errors, - "ERC721NonexistentToken" + await rewardNft.mint(subject) + + await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( + policy, + "UnsuccessfulCheck" ) }) it("reverts when check fails", async () => { - const { nft, deployer, voting, policy, notOwner, subject, validNFTId } = + const { signupNft, rewardNft, deployer, voting, policy, notOwner, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) await policy.setTarget(await voting.getAddress()) - await nft.connect(deployer).mint(notOwner) + 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 expect(voting.connect(subject).reward(1)).to.be.revertedWithCustomError( + await rewardNft.connect(deployer).mint(subject) + + await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( policy, "UnsuccessfulCheck" ) }) it("reverts when not registered", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + const { voting, policy, notOwner } = await loadFixture(deployAdvancedVotingFixture) await policy.setTarget(await notOwner.getAddress()) - await expect(voting.connect(notOwner).reward(validNFTId)).to.be.revertedWithCustomError( - voting, - "NotRegistered" - ) + await expect(voting.connect(notOwner).eligible()).to.be.revertedWithCustomError(voting, "NotRegistered") }) it("reverts when not voted", async () => { @@ -1322,13 +1392,10 @@ describe("Advanced", () => { await policy.setTarget(await voting.getAddress()) await voting.connect(subject).register(validNFTId) - await expect(voting.connect(subject).reward(validNFTId)).to.be.revertedWithCustomError( - voting, - "NotVoted" - ) + await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError(voting, "NotVoted") }) - it("claims reward successfully", async () => { + it("verifies eligibility successfully", async () => { const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = await loadFixture(deployAdvancedVotingFixture) const targetAddress = await voting.getAddress() @@ -1337,7 +1404,7 @@ describe("Advanced", () => { await voting.connect(subject).register(validNFTId) await voting.connect(subject).vote(0) - const tx = await voting.connect(subject).reward(validNFTId) + const tx = await voting.connect(subject).eligible() const receipt = await tx.wait() const event = AdvancedVotingFactory.interface.parseLog( receipt?.logs[1] as unknown as { topics: string[]; data: string } @@ -1356,17 +1423,17 @@ describe("Advanced", () => { expect(await voting.voteCounts(1)).to.be.equal(0) }) - it("reverts when already claimed", async () => { + it("reverts when already eligible", async () => { const { voting, policy, subject, validNFTId } = 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).reward(validNFTId) + await voting.connect(subject).eligible() - await expect(voting.connect(subject).reward(validNFTId)).to.be.revertedWithCustomError( + await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( voting, - "AlreadyClaimed" + "AlreadyEligible" ) }) }) @@ -1375,23 +1442,30 @@ describe("Advanced", () => { 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 nft: NFT = await NFTFactory.deploy() - - const checker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await nft.getAddress()], + 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()], 1, 0, - 20 + 10 ) const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress(), + await advancedChecker.getAddress(), false, false, true @@ -1409,7 +1483,7 @@ describe("Advanced", () => { const voterAddress = await voter.getAddress() // mint for voter. - await nft.connect(deployer).mint(voterAddress) + await signupNft.connect(deployer).mint(voterAddress) // register. await voting.connect(voter).register(tokenId) @@ -1418,7 +1492,7 @@ describe("Advanced", () => { await voting.connect(voter).vote(tokenId % 2) // reward. - await voting.connect(voter).reward(tokenId) + await voting.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) diff --git a/packages/contracts/test/Base.test.ts b/packages/contracts/test/Base.test.ts index 53143e7..21f6fdc 100644 --- a/packages/contracts/test/Base.test.ts +++ b/packages/contracts/test/Base.test.ts @@ -66,6 +66,33 @@ describe("Base", () => { }) }) + 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("reverts when index out of bounds", async () => { + const { checkerHarness } = await loadFixture(deployBaseCheckerFixture) + await expect(checkerHarness.exposed__getVerifierAtIndex(1)).to.be.revertedWithCustomError( + checkerHarness, + "VerifierNotFound" + ) + }) + }) + describe("check", () => { it("reverts when evidence is invalid", async () => { const { nft, checker, target, subjectAddress, invalidNFTId } =