From 30dd8b9e56f07a1d25d536fe019bc43470f6cb4f Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 21 Jan 2025 11:59:18 +0100 Subject: [PATCH 01/12] refactor(contracts): remove double mapping from policy --- .../contracts/src/AdvancedPolicy.sol | 6 +-- .../contracts/contracts/src/BasePolicy.sol | 8 ++-- .../src/test/advanced/AdvancedVoting.sol | 4 +- .../contracts/src/test/base/BaseVoting.sol | 2 +- packages/contracts/test/Advanced.test.ts | 40 +++++++++---------- packages/contracts/test/Base.test.ts | 6 +-- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/packages/contracts/contracts/src/AdvancedPolicy.sol b/packages/contracts/contracts/src/AdvancedPolicy.sol index 3d8ccde..3eae933 100644 --- a/packages/contracts/contracts/src/AdvancedPolicy.sol +++ b/packages/contracts/contracts/src/AdvancedPolicy.sol @@ -23,8 +23,8 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { bool public immutable ALLOW_MULTIPLE_MAIN; /// @notice Tracks validation status for each subject per target. - /// @dev Maps target => subject => CheckStatus. - mapping(address => mapping(address => CheckStatus)) public enforced; + /// @dev Maps subject => CheckStatus. + mapping(address => CheckStatus) public enforced; /// @notice Initializes contract with an AdvancedChecker instance and checks configs. /// @param _advancedChecker Address of the AdvancedChecker contract. @@ -64,7 +64,7 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { revert UnsuccessfulCheck(); } - CheckStatus storage status = enforced[msg.sender][subject]; + CheckStatus storage status = enforced[subject]; // Handle PRE check. if (checkType == Check.PRE) { diff --git a/packages/contracts/contracts/src/BasePolicy.sol b/packages/contracts/contracts/src/BasePolicy.sol index c9e9c5d..36d52ec 100644 --- a/packages/contracts/contracts/src/BasePolicy.sol +++ b/packages/contracts/contracts/src/BasePolicy.sol @@ -18,8 +18,8 @@ abstract contract BasePolicy is Policy, IBasePolicy { BaseChecker public immutable BASE_CHECKER; /// @notice Tracks enforcement status for each subject per target. - /// @dev Maps target => subject => enforcement status. - mapping(address => mapping(address => bool)) public enforced; + /// @dev Maps subject => enforcement status. + mapping(address => bool) public enforced; /// @notice Initializes the contract with a BaseChecker instance. /// @param _baseChecker Address of the BaseChecker contract. @@ -48,10 +48,10 @@ abstract contract BasePolicy is Policy, IBasePolicy { function _enforce(address subject, bytes[] memory evidence) internal { bool checked = BASE_CHECKER.check(subject, evidence); - if (enforced[msg.sender][subject]) revert AlreadyEnforced(); + if (enforced[subject]) revert AlreadyEnforced(); if (!checked) revert UnsuccessfulCheck(); - enforced[msg.sender][subject] = checked; + enforced[subject] = checked; emit Enforced(subject, target, evidence); } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol index 8d69c78..75ef15b 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol @@ -67,7 +67,7 @@ contract AdvancedVoting { /// - Token balance must meet requirements. /// @custom:emits Voted when vote is recorded. function vote(uint8 option) external { - (bool pre, , ) = POLICY.enforced(address(this), msg.sender); + (bool pre, , ) = POLICY.enforced(msg.sender); if (!pre) revert NotRegistered(); if (option >= 2) revert InvalidOption(); @@ -92,7 +92,7 @@ contract AdvancedVoting { /// - Caller must meet eligibility criteria (no existing benefits). /// @custom:emits Eligible when verification succeeds. function eligible() external { - (bool pre, uint8 main, bool post) = POLICY.enforced(address(this), msg.sender); + (bool pre, uint8 main, bool post) = POLICY.enforced(msg.sender); if (!pre) revert NotRegistered(); if (main == 0) revert NotVoted(); diff --git a/packages/contracts/contracts/src/test/base/BaseVoting.sol b/packages/contracts/contracts/src/test/base/BaseVoting.sol index 4f37794..4e50c80 100644 --- a/packages/contracts/contracts/src/test/base/BaseVoting.sol +++ b/packages/contracts/contracts/src/test/base/BaseVoting.sol @@ -58,7 +58,7 @@ contract BaseVoting { /// @custom:emits Voted on successful vote cast. function vote(uint8 option) external { // Verify registration and voting status. - if (!POLICY.enforced(address(this), msg.sender)) revert NotRegistered(); + if (!POLICY.enforced(msg.sender)) revert NotRegistered(); if (hasVoted[msg.sender]) revert AlreadyVoted(); if (option >= 2) revert InvalidOption(); diff --git a/packages/contracts/test/Advanced.test.ts b/packages/contracts/test/Advanced.test.ts index ce76a40..690935a 100644 --- a/packages/contracts/test/Advanced.test.ts +++ b/packages/contracts/test/Advanced.test.ts @@ -558,7 +558,7 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(0) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) }) it("reverts when pre already enforced", async () => { @@ -625,7 +625,7 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1) }) it("executes multiple mains when allowed", async () => { @@ -655,7 +655,7 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2) }) it("executes multiple mains when allowed", async () => { @@ -769,7 +769,7 @@ describe("Advanced", () => { expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) expect(event.args.checkType).to.eq(2) - expect((await policy.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) }) it("reverts when post already enforced", async () => { @@ -859,7 +859,7 @@ describe("Advanced", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) + expect((await policyHarness.enforced(subjectAddress))[0]).to.be.equal(true) }) it("reverts when pre already enforced", async () => { @@ -926,7 +926,7 @@ describe("Advanced", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1) + expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(1) }) it("executes multiple mains when allowed", async () => { @@ -956,7 +956,7 @@ describe("Advanced", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2) + expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(2) }) it("executes multiple mains when allowed", async () => { @@ -1073,7 +1073,7 @@ describe("Advanced", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) + expect((await policyHarness.enforced(subjectAddress))[2]).to.be.equal(true) }) it("reverts when post already enforced", async () => { @@ -1232,8 +1232,8 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(0n) + expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(0n) expect(await voting.voteCounts(0)).to.be.equal(0) expect(await voting.voteCounts(1)).to.be.equal(0) }) @@ -1294,8 +1294,8 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) expect(event.args.option).to.eq(option) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1n) + expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) expect(await voting.voteCounts(0)).to.be.equal(1) expect(await voting.voteCounts(1)).to.be.equal(0) }) @@ -1324,8 +1324,8 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) expect(event.args.option).to.eq(option) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(2n) + expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2n) expect(await voting.voteCounts(0)).to.be.equal(2) expect(await voting.voteCounts(1)).to.be.equal(0) }) @@ -1416,9 +1416,9 @@ describe("Advanced", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(targetAddress, subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(targetAddress, subjectAddress))[1]).to.be.equal(1n) - expect((await policy.enforced(targetAddress, subjectAddress))[2]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) + expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) expect(await voting.voteCounts(0)).to.be.equal(1) expect(await voting.voteCounts(1)).to.be.equal(0) }) @@ -1494,9 +1494,9 @@ describe("Advanced", () => { // reward. 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) - expect((await policy.enforced(targetAddress, voterAddress))[2]).to.be.equal(true) + expect((await policy.enforced(voterAddress))[0]).to.be.equal(true) + expect((await policy.enforced(voterAddress))[1]).to.be.equal(1) + expect((await policy.enforced(voterAddress))[2]).to.be.equal(true) } }) }) diff --git a/packages/contracts/test/Base.test.ts b/packages/contracts/test/Base.test.ts index 21f6fdc..0bf5b52 100644 --- a/packages/contracts/test/Base.test.ts +++ b/packages/contracts/test/Base.test.ts @@ -317,7 +317,7 @@ describe("Base", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policy.enforced(targetAddress, subjectAddress)).to.be.equal(true) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) }) it("reverts when already enforced", async () => { @@ -389,7 +389,7 @@ describe("Base", () => { expect(event.args.subject).to.eq(subjectAddress) expect(event.args.target).to.eq(targetAddress) expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policyHarness.enforced(targetAddress, subjectAddress)).to.be.equal(true) + expect(await policyHarness.enforced(subjectAddress)).to.be.equal(true) }) it("reverts when already enforced", async () => { @@ -520,7 +520,7 @@ describe("Base", () => { expect(receipt?.status).to.eq(1) expect(event.args.voter).to.eq(subjectAddress) - expect(await policy.enforced(targetAddress, subjectAddress)).to.be.equal(true) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) expect(await voting.hasVoted(subjectAddress)).to.be.equal(false) expect(await voting.voteCounts(0)).to.be.equal(0) expect(await voting.voteCounts(1)).to.be.equal(0) From 11a27d2d35854b7b1e1bd8435c7b6426fdce4cd7 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 21 Jan 2025 14:39:28 +0100 Subject: [PATCH 02/12] style(contracts): better folder organization for contracts --- .../src/{ => core/checker}/AdvancedChecker.sol | 2 +- .../src/{ => core/checker}/BaseChecker.sol | 2 +- .../contracts/src/{ => core/checker}/Checker.sol | 2 +- .../{ => core}/interfaces/IAdvancedChecker.sol | 0 .../src/{ => core}/interfaces/IAdvancedPolicy.sol | 0 .../src/{ => core}/interfaces/IBaseChecker.sol | 0 .../src/{ => core}/interfaces/IBasePolicy.sol | 0 .../src/{ => core}/interfaces/IChecker.sol | 0 .../contracts/src/core/interfaces/IFactory.sol | 15 +++++++++++++++ .../src/{ => core}/interfaces/IPolicy.sol | 0 .../src/{ => core/policy}/AdvancedPolicy.sol | 4 ++-- .../src/{ => core/policy}/BasePolicy.sol | 4 ++-- .../contracts/src/{ => core/policy}/Policy.sol | 2 +- .../contracts/contracts/src/test/Advanced.t.sol | 8 ++++---- packages/contracts/contracts/src/test/Base.t.sol | 4 ++-- .../src/test/advanced/AdvancedERC721Checker.sol | 2 +- .../src/test/advanced/AdvancedERC721Policy.sol | 2 +- .../src/test/advanced/AdvancedVoting.sol | 4 ++-- .../contracts/src/test/base/BaseERC721Checker.sol | 2 +- .../contracts/src/test/base/BaseERC721Policy.sol | 2 +- .../wrappers/AdvancedERC721CheckerHarness.sol | 2 +- .../test/wrappers/AdvancedERC721PolicyHarness.sol | 2 +- 22 files changed, 37 insertions(+), 22 deletions(-) rename packages/contracts/contracts/src/{ => core/checker}/AdvancedChecker.sol (96%) rename packages/contracts/contracts/src/{ => core/checker}/BaseChecker.sol (95%) rename packages/contracts/contracts/src/{ => core/checker}/Checker.sol (97%) rename packages/contracts/contracts/src/{ => core}/interfaces/IAdvancedChecker.sol (100%) rename packages/contracts/contracts/src/{ => core}/interfaces/IAdvancedPolicy.sol (100%) rename packages/contracts/contracts/src/{ => core}/interfaces/IBaseChecker.sol (100%) rename packages/contracts/contracts/src/{ => core}/interfaces/IBasePolicy.sol (100%) rename packages/contracts/contracts/src/{ => core}/interfaces/IChecker.sol (100%) create mode 100644 packages/contracts/contracts/src/core/interfaces/IFactory.sol rename packages/contracts/contracts/src/{ => core}/interfaces/IPolicy.sol (100%) rename packages/contracts/contracts/src/{ => core/policy}/AdvancedPolicy.sol (96%) rename packages/contracts/contracts/src/{ => core/policy}/BasePolicy.sol (95%) rename packages/contracts/contracts/src/{ => core/policy}/Policy.sol (97%) diff --git a/packages/contracts/contracts/src/AdvancedChecker.sol b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol similarity index 96% rename from packages/contracts/contracts/src/AdvancedChecker.sol rename to packages/contracts/contracts/src/core/checker/AdvancedChecker.sol index 922b245..c8143c7 100644 --- a/packages/contracts/contracts/src/AdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/checker/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, CheckStatus} from "../interfaces/IAdvancedChecker.sol"; import {Checker} from "./Checker.sol"; /// @title AdvancedChecker. diff --git a/packages/contracts/contracts/src/BaseChecker.sol b/packages/contracts/contracts/src/core/checker/BaseChecker.sol similarity index 95% rename from packages/contracts/contracts/src/BaseChecker.sol rename to packages/contracts/contracts/src/core/checker/BaseChecker.sol index bbb106b..e9fc19b 100644 --- a/packages/contracts/contracts/src/BaseChecker.sol +++ b/packages/contracts/contracts/src/core/checker/BaseChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IBaseChecker} from "./interfaces/IBaseChecker.sol"; +import {IBaseChecker} from "../interfaces/IBaseChecker.sol"; import {Checker} from "./Checker.sol"; /// @title BaseChecker diff --git a/packages/contracts/contracts/src/Checker.sol b/packages/contracts/contracts/src/core/checker/Checker.sol similarity index 97% rename from packages/contracts/contracts/src/Checker.sol rename to packages/contracts/contracts/src/core/checker/Checker.sol index 83cc4a6..6da0a2b 100644 --- a/packages/contracts/contracts/src/Checker.sol +++ b/packages/contracts/contracts/src/core/checker/Checker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IChecker} from "./interfaces/IChecker.sol"; +import {IChecker} from "../interfaces/IChecker.sol"; /// @title Checker /// @notice Abstract base contract for implementing attribute verification logic. diff --git a/packages/contracts/contracts/src/interfaces/IAdvancedChecker.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IAdvancedChecker.sol rename to packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol diff --git a/packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IAdvancedPolicy.sol rename to packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol diff --git a/packages/contracts/contracts/src/interfaces/IBaseChecker.sol b/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IBaseChecker.sol rename to packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol diff --git a/packages/contracts/contracts/src/interfaces/IBasePolicy.sol b/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IBasePolicy.sol rename to packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol diff --git a/packages/contracts/contracts/src/interfaces/IChecker.sol b/packages/contracts/contracts/src/core/interfaces/IChecker.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IChecker.sol rename to packages/contracts/contracts/src/core/interfaces/IChecker.sol diff --git a/packages/contracts/contracts/src/core/interfaces/IFactory.sol b/packages/contracts/contracts/src/core/interfaces/IFactory.sol new file mode 100644 index 0000000..dbe76e9 --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IFactory.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {LibClone} from "solady/src/utils/LibClone.sol"; + +/// @title IFactory +/// @notice Base interface for Excubiae factories +interface IFactory { + /// @notice Emitted when a new clone is deployed + /// @param instance Address of the deployed clone + event CloneDeployed(address indexed instance); + + /// @notice Returns the implementation contract address + function implementation() external view returns (address); +} diff --git a/packages/contracts/contracts/src/interfaces/IPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol similarity index 100% rename from packages/contracts/contracts/src/interfaces/IPolicy.sol rename to packages/contracts/contracts/src/core/interfaces/IPolicy.sol diff --git a/packages/contracts/contracts/src/AdvancedPolicy.sol b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol similarity index 96% rename from packages/contracts/contracts/src/AdvancedPolicy.sol rename to packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol index 3eae933..647ed9f 100644 --- a/packages/contracts/contracts/src/AdvancedPolicy.sol +++ b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.20; import {Policy} from "./Policy.sol"; -import {IAdvancedPolicy, Check} from "./interfaces/IAdvancedPolicy.sol"; -import {AdvancedChecker, CheckStatus} from "./AdvancedChecker.sol"; +import {IAdvancedPolicy, Check} from "../interfaces/IAdvancedPolicy.sol"; +import {AdvancedChecker, CheckStatus} from "../checker/AdvancedChecker.sol"; /// @title AdvancedPolicy. /// @notice Implements advanced policy checks with pre, main, and post validation stages. diff --git a/packages/contracts/contracts/src/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol similarity index 95% rename from packages/contracts/contracts/src/BasePolicy.sol rename to packages/contracts/contracts/src/core/policy/BasePolicy.sol index 36d52ec..e9e4582 100644 --- a/packages/contracts/contracts/src/BasePolicy.sol +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IBasePolicy} from "./interfaces/IBasePolicy.sol"; +import {IBasePolicy} from "../interfaces/IBasePolicy.sol"; import {Policy} from "./Policy.sol"; -import {BaseChecker} from "./BaseChecker.sol"; +import {BaseChecker} from "../checker/BaseChecker.sol"; /// @title BasePolicy /// @notice Abstract base contract for implementing specific policy checks. diff --git a/packages/contracts/contracts/src/Policy.sol b/packages/contracts/contracts/src/core/policy/Policy.sol similarity index 97% rename from packages/contracts/contracts/src/Policy.sol rename to packages/contracts/contracts/src/core/policy/Policy.sol index 40234cf..287fbdd 100644 --- a/packages/contracts/contracts/src/Policy.sol +++ b/packages/contracts/contracts/src/core/policy/Policy.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {IPolicy} from "./interfaces/IPolicy.sol"; +import {IPolicy} from "../interfaces/IPolicy.sol"; /// @title Policy /// @notice Implements a base policy contract that protects access to a target contract diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index f84fb3f..a25d4dd 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -9,12 +9,12 @@ import {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol"; import {AdvancedVoting} from "./advanced/AdvancedVoting.sol"; import {AdvancedERC721CheckerHarness} from "./wrappers/AdvancedERC721CheckerHarness.sol"; import {AdvancedERC721PolicyHarness} from "./wrappers/AdvancedERC721PolicyHarness.sol"; -import {IChecker} from "../interfaces/IChecker.sol"; -import {IPolicy} from "../interfaces/IPolicy.sol"; -import {IAdvancedPolicy} from "../interfaces/IAdvancedPolicy.sol"; +import {IChecker} from "../core/interfaces/IChecker.sol"; +import {IPolicy} from "../core/interfaces/IPolicy.sol"; +import {IAdvancedPolicy} from "../core/interfaces/IAdvancedPolicy.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {Check} from "../interfaces/IAdvancedChecker.sol"; +import {Check} from "../core/interfaces/IAdvancedChecker.sol"; contract AdvancedChecker is Test { NFT internal signupNft; diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index 60f9abc..5f5bea5 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -8,8 +8,8 @@ import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; import {BaseVoting} from "./base/BaseVoting.sol"; import {BaseERC721CheckerHarness} from "./wrappers/BaseERC721CheckerHarness.sol"; import {BaseERC721PolicyHarness} from "./wrappers/BaseERC721PolicyHarness.sol"; -import {IPolicy} from "../interfaces/IPolicy.sol"; -import {IChecker} from "../interfaces/IChecker.sol"; +import {IPolicy} from "../core/interfaces/IPolicy.sol"; +import {IChecker} from "../core/interfaces/IChecker.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index 644b944..b205802 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedChecker} from "../../AdvancedChecker.sol"; +import {AdvancedChecker} from "../../core/checker/AdvancedChecker.sol"; import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol index b852777..93bded0 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedPolicy} from "../../AdvancedPolicy.sol"; +import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; /// @title AdvancedERC721Policy. diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol index 75ef15b..487b9de 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedPolicy} from "../../AdvancedPolicy.sol"; -import {Check} from "../../interfaces/IAdvancedPolicy.sol"; +import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; +import {Check} from "../../core/interfaces/IAdvancedPolicy.sol"; /// @title AdvancedVoting. /// @notice Advanced voting system with NFT-based phases and eligibility verification. diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol index 759fc02..0828bd0 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BaseChecker} from "../../../src/BaseChecker.sol"; +import {BaseChecker} from "../../core/checker/BaseChecker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title BaseERC721Checker. diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol index 2b112ce..0aa3dc3 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BasePolicy} from "../../../src/BasePolicy.sol"; +import {BasePolicy} from "../../core/policy/BasePolicy.sol"; import {BaseERC721Checker} from "./BaseERC721Checker.sol"; /// @title BaseERC721Policy. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol index a48995e..6537b43 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.20; import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; -import {Check} from "../../interfaces/IAdvancedChecker.sol"; +import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; /// @title AdvancedERC721CheckerHarness. /// @notice Test harness exposing internal methods of AdvancedERC721Checker. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol index 46a1518..d77bd66 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol"; import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; -import {Check} from "../../interfaces/IAdvancedChecker.sol"; +import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; /// @title AdvancedERC721PolicyHarness. /// @notice Test harness for AdvancedERC721Policy internal methods. From ba73ba0e85ee10b331db67a2ecd9e84af0bc26e7 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Thu, 23 Jan 2025 19:02:35 +0100 Subject: [PATCH 03/12] chore(contracts): add solady lib --- packages/contracts/contracts/package.json | 3 ++- packages/contracts/package.json | 3 ++- packages/contracts/remappings.txt | 3 ++- yarn.lock | 9 +++++++++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/contracts/contracts/package.json b/packages/contracts/contracts/package.json index ca52c03..d5551d9 100644 --- a/packages/contracts/contracts/package.json +++ b/packages/contracts/contracts/package.json @@ -27,6 +27,7 @@ }, "packageManager": "yarn@4.5.0", "dependencies": { - "@openzeppelin/contracts": "^5.0.2" + "@openzeppelin/contracts": "^5.0.2", + "solady": "^0.0.298" } } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 327308e..83d29ac 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -49,6 +49,7 @@ "typescript": "5.3" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.2" + "@openzeppelin/contracts": "^5.0.2", + "solady": "^0.0.298" } } diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt index b0866a6..0ca80f4 100644 --- a/packages/contracts/remappings.txt +++ b/packages/contracts/remappings.txt @@ -1,2 +1,3 @@ @openzeppelin/=node_modules/@openzeppelin/ -forge-std/=node_modules/forge-std/ +@solady/=node_modules/solady/ +forge-std/=node_modules/forge-std/ \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ad764ce..1e0168a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3107,6 +3107,7 @@ __metadata: resolution: "@excubiae/contracts@workspace:packages/contracts/contracts" dependencies: "@openzeppelin/contracts": "npm:^5.0.2" + solady: "npm:^0.0.298" languageName: unknown linkType: soft @@ -9394,6 +9395,7 @@ __metadata: forge-std: "github:foundry-rs/forge-std#v1.9.2" hardhat: "npm:^2.22.11" hardhat-gas-reporter: "npm:^1.0.8" + solady: "npm:^0.0.298" solhint: "npm:^5.0.3" solidity-coverage: "npm:^0.8.13" ts-node: "npm:^10.9.2" @@ -17724,6 +17726,13 @@ __metadata: languageName: node linkType: hard +"solady@npm:^0.0.298": + version: 0.0.298 + resolution: "solady@npm:0.0.298" + checksum: 10/b337d65e0f3aadabcb981a6bbf71a4097cc9e0f91afb354a77352a45d933109834568c5e3e3c90c30a29809b95aeb0eb8da4574dbffa18ff451064f52a0fa4a0 + languageName: node + linkType: hard + "solc@npm:0.8.26": version: 0.8.26 resolution: "solc@npm:0.8.26" From a2eb7911a39085ab20e69d381fc9810871f2eb63 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Fri, 24 Jan 2025 19:49:07 +0100 Subject: [PATCH 04/12] feat(contracts): draft minimal proxy pattern with immutable args implementation --- .../src/core/checker/AdvancedChecker.sol | 4 +- .../src/core/checker/BaseChecker.sol | 2 - .../contracts/src/core/checker/Checker.sol | 37 +- .../src/core/interfaces/IAdvancedChecker.sol | 4 +- .../src/core/interfaces/IChecker.sol | 6 - .../contracts/src/core/interfaces/IPolicy.sol | 1 + .../src/core/policy/AdvancedPolicy.sol | 95 +- .../contracts/src/core/policy/BasePolicy.sol | 42 +- .../contracts/src/core/policy/Policy.sol | 64 +- .../contracts/src/test/Advanced.t.sol | 1590 ++++----- .../contracts/contracts/src/test/Base.t.sol | 631 ++-- .../test/advanced/AdvancedERC721Checker.sol | 62 +- .../advanced/AdvancedERC721CheckerFactory.sol | 39 + .../test/advanced/AdvancedERC721Policy.sol | 8 - .../advanced/AdvancedERC721PolicyFactory.sol | 41 + .../src/test/base/BaseERC721Checker.sol | 21 +- .../test/base/BaseERC721CheckerFactory.sol | 25 + .../src/test/base/BaseERC721Policy.sol | 23 +- .../src/test/base/BaseERC721PolicyFactory.sol | 36 + .../wrappers/AdvancedERC721CheckerHarness.sol | 49 +- .../wrappers/AdvancedERC721PolicyHarness.sol | 20 +- .../wrappers/BaseERC721CheckerHarness.sol | 17 +- .../test/wrappers/BaseERC721PolicyHarness.sol | 10 +- packages/contracts/test/Advanced.test.ts | 2987 ++++++++--------- packages/contracts/test/Base.test.ts | 1268 ++++--- 25 files changed, 3620 insertions(+), 3462 deletions(-) create mode 100644 packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol create mode 100644 packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol create mode 100644 packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol create mode 100644 packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol diff --git a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol index c8143c7..aa0c250 100644 --- a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol @@ -7,9 +7,7 @@ import {Checker} from "./Checker.sol"; /// @title AdvancedChecker. /// @notice Multi-phase validation checker with pre, main, and post checks. /// @dev Base contract for implementing complex validation logic with configurable check phases. -abstract contract AdvancedChecker is IAdvancedChecker, Checker { - constructor(address[] memory _verifiers) Checker(_verifiers) {} - +abstract contract AdvancedChecker is Checker, IAdvancedChecker { /// @notice Entry point for validation checks. /// @param subject Address to validate. /// @param evidence Validation data. diff --git a/packages/contracts/contracts/src/core/checker/BaseChecker.sol b/packages/contracts/contracts/src/core/checker/BaseChecker.sol index e9fc19b..00df3d9 100644 --- a/packages/contracts/contracts/src/core/checker/BaseChecker.sol +++ b/packages/contracts/contracts/src/core/checker/BaseChecker.sol @@ -9,8 +9,6 @@ import {Checker} from "./Checker.sol"; /// @dev Provides a standardized interface for implementing custom validation logic /// through the internal _check method. abstract contract BaseChecker is Checker, IBaseChecker { - constructor(address[] memory _verifiers) Checker(_verifiers) {} - /// @notice Validates evidence for a given subject address. /// @dev External view function that delegates to internal _check implementation. /// @param subject Address to validate. diff --git a/packages/contracts/contracts/src/core/checker/Checker.sol b/packages/contracts/contracts/src/core/checker/Checker.sol index 6da0a2b..b6ab9a5 100644 --- a/packages/contracts/contracts/src/core/checker/Checker.sol +++ b/packages/contracts/contracts/src/core/checker/Checker.sol @@ -2,39 +2,20 @@ pragma solidity ^0.8.20; import {IChecker} from "../interfaces/IChecker.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; -/// @title Checker -/// @notice Abstract base contract for implementing attribute verification logic. -/// @dev Provides infrastructure to orchestrate third-party verifiers for single checks. +// @todo refactoring & comments abstract contract Checker is IChecker { - /// @notice Array of third-party contract addresses used for verification. - /// @dev Can include existing and already deployed Checkers, NFTs, MACI polls, and/or any other contract - /// that provides evidence verification. These contracts should already be deployed and operational. - address[] internal verifiers; + bool private _initialized; - /// @notice Initializes the Checker with an optional list of third-party verification contracts. - /// @param _verifiers Array of addresses for existing verification contracts. - /// @dev Each address should point to a deployed contract that will be consulted during verification. - /// This array can remain empty if there's no reliance on external verifiers. - constructor(address[] memory _verifiers) { - verifiers = _verifiers; - } + error AlreadyInitialized(); - /// @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); + function initialize() public virtual { + if (_initialized) revert AlreadyInitialized(); + _initialized = true; } - /// @notice Internal implementation of verifier address retrieval at a specific index. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - function _getVerifierAtIndex(uint256 index) internal view returns (address) { - if (index >= verifiers.length) revert VerifierNotFound(); - - return verifiers[index]; + function _getAppendedBytes() internal view returns (bytes memory) { + return LibClone.argsOnClone(address(this)); } } diff --git a/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol index 67d27e1..c75fbdf 100644 --- a/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IChecker} from "./IChecker.sol"; - /// @title Check. /// @notice Defines validation phases in the AdvancedChecker system. /// @custom:values PRE - Pre-condition validation. @@ -28,7 +26,7 @@ struct CheckStatus { /// @title IAdvancedChecker. /// @notice Defines multi-phase validation system interface. /// @dev Implement this for custom validation logic with pre/main/post checks. -interface IAdvancedChecker is IChecker { +interface IAdvancedChecker { /// @notice Validates subject against specified check type. /// @param subject Address to validate. /// @param evidence Validation data. diff --git a/packages/contracts/contracts/src/core/interfaces/IChecker.sol b/packages/contracts/contracts/src/core/interfaces/IChecker.sol index f8898a9..3b1b847 100644 --- a/packages/contracts/contracts/src/core/interfaces/IChecker.sol +++ b/packages/contracts/contracts/src/core/interfaces/IChecker.sol @@ -6,10 +6,4 @@ 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/core/interfaces/IPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol index 9541a64..4fed982 100644 --- a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol +++ b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol @@ -14,6 +14,7 @@ interface IPolicy { error TargetOnly(); error TargetAlreadySet(); error AlreadyEnforced(); + error AlreadyInitialized(); /// @notice Returns policy trait identifier. /// @return Policy trait string (e.g., "Semaphore"). diff --git a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol index 647ed9f..8b59309 100644 --- a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol +++ b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol @@ -1,64 +1,60 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Policy} from "./Policy.sol"; import {IAdvancedPolicy, Check} from "../interfaces/IAdvancedPolicy.sol"; import {AdvancedChecker, CheckStatus} from "../checker/AdvancedChecker.sol"; +import {Policy} from "./Policy.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; -/// @title AdvancedPolicy. +/// @title AdvancedPolicy /// @notice Implements advanced policy checks with pre, main, and post validation stages. -/// @dev Extends Policy contract with multi-stage validation capabilities. +/// @dev Extends Policy with multi-stage validation. Now clone-friendly with `initialize()`. abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { - /// @notice Reference to the validation checker contract. - /// @dev Immutable to ensure checker cannot be changed after deployment. - AdvancedChecker public immutable ADVANCED_CHECKER; + /// @notice Reference to the validation checker contract. Stored, not immutable. + AdvancedChecker public ADVANCED_CHECKER; /// @notice Controls whether pre-condition checks are required. - bool public immutable SKIP_PRE; + bool public SKIP_PRE; /// @notice Controls whether post-condition checks are required. - bool public immutable SKIP_POST; + bool public SKIP_POST; /// @notice Controls whether main check can be executed multiple times. - bool public immutable ALLOW_MULTIPLE_MAIN; + bool public ALLOW_MULTIPLE_MAIN; /// @notice Tracks validation status for each subject per target. - /// @dev Maps subject => CheckStatus. mapping(address => CheckStatus) public enforced; - /// @notice Initializes contract with an AdvancedChecker instance and checks configs. - /// @param _advancedChecker Address of the AdvancedChecker contract. - /// @param _skipPre Skip pre-condition validation. - /// @param _skipPost Skip post-condition validation. - /// @param _allowMultipleMain Allow multiple main validations. - constructor(AdvancedChecker _advancedChecker, bool _skipPre, bool _skipPost, bool _allowMultipleMain) { - ADVANCED_CHECKER = _advancedChecker; - SKIP_PRE = _skipPre; - SKIP_POST = _skipPost; - ALLOW_MULTIPLE_MAIN = _allowMultipleMain; + /** + * @notice Initialize function for minimal proxy clones. + * Decodes appended bytes for (AdvancedChecker, skipPre, skipPost, allowMultipleMain). + */ + function initialize() public virtual override { + // 1. Call Policy’s initialize to set ownership and `_initialized`. + super.initialize(); + + // 2. Decode the appended bytes for the advanced config. + bytes memory data = _getAppendedBytes(); + (address sender, address advCheckerAddr, bool skipPre, bool skipPost, bool allowMultipleMain) = abi.decode( + data, + (address, address, bool, bool, bool) + ); + + _transferOwnership(sender); + + ADVANCED_CHECKER = AdvancedChecker(advCheckerAddr); + SKIP_PRE = skipPre; + SKIP_POST = skipPost; + ALLOW_MULTIPLE_MAIN = allowMultipleMain; } - /// @notice Enforces policy check for a subject. - /// @dev Only callable by target contract. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check (PRE, MAIN, POST). + /// @notice Enforces a policy check for a subject, handling multi-stage logic. + /// @dev Only callable by the target contract. function enforce(address subject, bytes[] calldata evidence, Check checkType) external override onlyTarget { _enforce(subject, evidence, checkType); } - /// @notice Internal check enforcement logic. - /// @dev Handles different check types and their dependencies. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check to perform. - /// @custom:throws CannotPreCheckWhenSkipped If PRE check attempted when skipped. - /// @custom:throws CannotPostCheckWhenSkipped If POST check attempted when skipped. - /// @custom:throws UnsuccessfulCheck If validation fails. - /// @custom:throws AlreadyEnforced If check was already completed. - /// @custom:throws PreCheckNotEnforced If PRE check is required but not done. - /// @custom:throws MainCheckNotEnforced If MAIN check is required but not done. - /// @custom:throws MainCheckAlreadyEnforced If multiple MAIN checks not allowed. + /// @notice Internal check enforcement logic for advanced multi-stage checks. function _enforce(address subject, bytes[] calldata evidence, Check checkType) internal { if (!ADVANCED_CHECKER.check(subject, evidence, checkType)) { revert UnsuccessfulCheck(); @@ -66,32 +62,19 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { CheckStatus storage status = enforced[subject]; - // Handle PRE check. if (checkType == Check.PRE) { if (SKIP_PRE) revert CannotPreCheckWhenSkipped(); - if (status.pre) { - revert AlreadyEnforced(); - } - + if (status.pre) revert AlreadyEnforced(); status.pre = true; } else if (checkType == Check.POST) { - // Handle POST check. if (SKIP_POST) revert CannotPostCheckWhenSkipped(); - if (status.main == 0) { - revert MainCheckNotEnforced(); - } - if (status.post) { - revert AlreadyEnforced(); - } + if (status.main == 0) revert MainCheckNotEnforced(); + if (status.post) revert AlreadyEnforced(); status.post = true; } else { - // Handle MAIN check. - if (!SKIP_PRE && !status.pre) { - revert PreCheckNotEnforced(); - } - if (!ALLOW_MULTIPLE_MAIN && status.main > 0) { - revert MainCheckAlreadyEnforced(); - } + // MAIN check + if (!SKIP_PRE && !status.pre) revert PreCheckNotEnforced(); + if (!ALLOW_MULTIPLE_MAIN && status.main > 0) revert MainCheckAlreadyEnforced(); status.main += 1; } diff --git a/packages/contracts/contracts/src/core/policy/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol index e9e4582..e42dc07 100644 --- a/packages/contracts/contracts/src/core/policy/BasePolicy.sol +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -4,54 +4,54 @@ pragma solidity ^0.8.20; import {IBasePolicy} from "../interfaces/IBasePolicy.sol"; import {Policy} from "./Policy.sol"; import {BaseChecker} from "../checker/BaseChecker.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title BasePolicy /// @notice Abstract base contract for implementing specific policy checks. /// @dev Inherits from Policy and implements IBasePolicy interface. -/// -/// Provides core functionality for enforcing policy checks through a BaseChecker -/// contract. Each specific policy implementation should extend this contract -/// and implement its custom checking logic. +/// Now uses an `initialize()` function for minimal proxy clones. abstract contract BasePolicy is Policy, IBasePolicy { /// @notice Reference to the BaseChecker contract used for validation. - /// @dev Immutable to ensure checker cannot be changed after deployment. - BaseChecker public immutable BASE_CHECKER; + /// @dev Stored in normal storage (not immutable) so it can be set in `initialize()`. + BaseChecker public BASE_CHECKER; /// @notice Tracks enforcement status for each subject per target. - /// @dev Maps subject => enforcement status. mapping(address => bool) public enforced; - /// @notice Initializes the contract with a BaseChecker instance. - /// @param _baseChecker Address of the BaseChecker contract. - /// @dev The BaseChecker address cannot be changed after deployment. - constructor(BaseChecker _baseChecker) { - BASE_CHECKER = _baseChecker; + /** + * @notice Initializes the contract with a BaseChecker instance, reading from appended bytes. + * Replaces the old constructor-based approach. + */ + function initialize() public virtual override { + // 1. Call the base `Policy.initialize()` to set ownership / handle `_initialized`. + super.initialize(); + + // 2. Decode the appended bytes to get the BaseChecker address (and anything else you might need). + bytes memory data = _getAppendedBytes(); + (address sender, address baseCheckerAddr) = abi.decode(data, (address, address)); + + _transferOwnership(sender); + + // 3. Store in the contract’s storage (previously `immutable`). + BASE_CHECKER = BaseChecker(baseCheckerAddr); } /// @notice External function to enforce policy checks. /// @dev Only callable by the target contract. /// @param subject Address to enforce the check on. /// @param evidence Additional data required for verification. - /// @custom:throws AlreadyEnforced if check was previously enforced. - /// @custom:throws UnsuccessfulCheck if the check fails. - /// @custom:emits Enforced when check succeeds. function enforce(address subject, bytes[] calldata evidence) external override onlyTarget { _enforce(subject, evidence); } /// @notice Internal implementation of enforcement logic. - /// @dev Performs the actual check using BASE_CHECKER. - /// @param subject Address to enforce the check on. - /// @param evidence Additional data required for verification. - /// @custom:throws AlreadyEnforced if already enforced for this subject. - /// @custom:throws UnsuccessfulCheck if BASE_CHECKER.check returns false. function _enforce(address subject, bytes[] memory evidence) internal { bool checked = BASE_CHECKER.check(subject, evidence); if (enforced[subject]) revert AlreadyEnforced(); if (!checked) revert UnsuccessfulCheck(); - enforced[subject] = checked; + enforced[subject] = true; emit Enforced(subject, target, evidence); } diff --git a/packages/contracts/contracts/src/core/policy/Policy.sol b/packages/contracts/contracts/src/core/policy/Policy.sol index 287fbdd..2d509bc 100644 --- a/packages/contracts/contracts/src/core/policy/Policy.sol +++ b/packages/contracts/contracts/src/core/policy/Policy.sol @@ -3,47 +3,57 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IPolicy} from "../interfaces/IPolicy.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; -/// @title Policy -/// @notice Implements a base policy contract that protects access to a target contract -/// @dev Inherits from OpenZeppelin's Ownable and implements IPolicy interface -/// -/// This contract serves as a base for implementing specific policy checks that must be -/// satisfied before interacting with a protected target contract. It provides core -/// functionality for managing the protected target address and access control. abstract contract Policy is IPolicy, Ownable(msg.sender) { - /// @notice The policy-protected contract address. - /// @dev This address can only be set once by the owner. - /// For example, the target is a Semaphore group that requires the subject - /// to meet certain criteria in order to join the group. + /// @notice One-time initialization guard. + bool private _initialized; + + /// @notice The “gatekeeped” contract address set once by the owner (if at all). address internal target; - /// @notice Restricts function access to only the target contract. - /// @dev Throws TargetOnly error if called by any other address. - modifier onlyTarget() { - if (msg.sender != target) revert TargetOnly(); - _; + /** + * @notice The base init. By default, transfers ownership to `msg.sender` (i.e., the caller). + * @dev If you want the factory to always be the owner, you just have the factory call this function, + * so `msg.sender` is the factory in that transaction. + */ + function initialize() public virtual { + if (_initialized) revert AlreadyInitialized(); + _initialized = true; + + // By default, set the owner to the caller (likely the factory). + // this is not the zero address as above! + _transferOwnership(msg.sender); + } + + function _getAppendedBytes() internal view returns (bytes memory) { + return LibClone.argsOnClone(address(this)); } - /// @notice Sets the target contract address. - /// @dev Can only be called once by the owner. - /// @param _target Address of the contract to be protected by this policy. - /// @custom:throws ZeroAddress if _target is the zero address. - /// @custom:throws TargetAlreadySet if target has already been set. - /// @custom:emits TargetSet when target is successfully set. + /** + * @notice Only the owner can call `setTarget` once. + * @param _target The contract to be protected by this policy. + */ function setTarget(address _target) external virtual onlyOwner { if (_target == address(0)) revert ZeroAddress(); if (target != address(0)) revert TargetAlreadySet(); target = _target; - emit TargetSet(_target); } - /// @notice Retrieves the current target contract address. - /// @return address The address of the policy-protected contract. - /// @dev Returns zero address if target hasn't been set yet. - function getTarget() public view returns (address) { + /** + * @notice A helper getter for the `target`. + */ + function getTarget() external view returns (address) { return target; } + + /** + * @notice A modifier that restricts a function to only be called by `target`. + */ + modifier onlyTarget() { + if (msg.sender != target) revert TargetOnly(); + _; + } } diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index a25d4dd..fe299ef 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -5,33 +5,37 @@ 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 {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; +import {AdvancedERC721CheckerFactory} from "./advanced/AdvancedERC721CheckerFactory.sol"; import {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol"; +import {AdvancedERC721PolicyFactory} from "./advanced/AdvancedERC721PolicyFactory.sol"; +import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; +import {BaseVoting} from "./base/BaseVoting.sol"; import {AdvancedVoting} from "./advanced/AdvancedVoting.sol"; -import {AdvancedERC721CheckerHarness} from "./wrappers/AdvancedERC721CheckerHarness.sol"; -import {AdvancedERC721PolicyHarness} from "./wrappers/AdvancedERC721PolicyHarness.sol"; -import {IChecker} from "../core/interfaces/IChecker.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; -import {IAdvancedPolicy} from "../core/interfaces/IAdvancedPolicy.sol"; +import {IChecker} from "../core/interfaces/IChecker.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Check} from "../core/interfaces/IAdvancedChecker.sol"; +// @todo add harnesses +// @todo add tests for factories + harnesses +// @todo add edge cases + contract AdvancedChecker is Test { NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; AdvancedERC721Checker internal advancedChecker; - AdvancedERC721CheckerHarness internal advancedCheckerHarness; + BaseERC721CheckerFactory internal baseFactory; + AdvancedERC721CheckerFactory internal advancedFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal baseVerifiers; - address[] internal advancedVerifiers; bytes[] public evidence = new bytes[](1); - bytes[] public wrongEvidence = new bytes[](1); function setUp() public virtual { vm.startPrank(deployer); @@ -39,248 +43,260 @@ contract AdvancedChecker is Test { signupNft = new NFT(); rewardNft = new NFT(); - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); + signupNft.mint(subject); + + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); + address baseClone = baseFactory.createERC721Checker(address(signupNft)); + baseChecker = BaseERC721Checker(baseClone); - advancedChecker = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - advancedCheckerHarness = new AdvancedERC721CheckerHarness(advancedVerifiers, 1, 0, 10); + address advancedClone = advancedFactory.createERC721Checker( + address(signupNft), + address(rewardNft), + address(baseChecker), + 1, + 0, + 10 + ); + + advancedChecker = AdvancedERC721Checker(advancedClone); evidence[0] = abi.encode(0); - wrongEvidence[0] = abi.encode(1); vm.stopPrank(); } - function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { - assertEq(advancedChecker.getVerifierAtIndex(0), address(signupNft)); + function test_simple() public { + bool result = advancedChecker.check(subject, evidence, Check.PRE); + assertTrue(result, "Expected subject to own token 0"); } - function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - advancedChecker.getVerifierAtIndex(5); - } + // @todo refactoring + // function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { + // assertEq(advancedChecker.getVerifierAtIndex(0), address(signupNft)); + // } - function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { - assertEq(advancedCheckerHarness.exposed__getVerifierAtIndex(0), address(signupNft)); - } + // function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { + // vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + // advancedChecker.getVerifierAtIndex(5); + // } - function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - advancedCheckerHarness.exposed__getVerifierAtIndex(5); - } + // function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { + // assertEq(advancedCheckerHarness.exposed__getVerifierAtIndex(0), address(signupNft)); + // } - function test_checkPre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + // function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { + // vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + // advancedCheckerHarness.exposed__getVerifierAtIndex(5); + // } - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - advancedChecker.check(subject, evidence, Check.PRE); + // function test_checkPre_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // advancedChecker.check(subject, evidence, Check.PRE); - function test_checkPre_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkPre_whenCallerNotOwner_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedChecker.check(notOwner, evidence, Check.PRE)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedChecker.check(notOwner, evidence, Check.PRE)); - function test_checkPre_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkPre_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedChecker.check(subject, evidence, Check.PRE)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedChecker.check(subject, evidence, Check.PRE)); - function test_checkMain_whenCallerHasNoTokens_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkMain_whenCallerHasNoTokens_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedChecker.check(notOwner, evidence, Check.MAIN)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedChecker.check(notOwner, evidence, Check.MAIN)); - function test_checkMain_whenCallerHasTokens_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkMain_whenCallerHasTokens_succeeds() public { + // vm.startPrank(target); - assert(advancedChecker.check(subject, evidence, Check.MAIN)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedChecker.check(subject, evidence, Check.MAIN)); - function test_checkPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - rewardNft.mint(subject); + // function test_checkPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedChecker.check(subject, evidence, Check.POST)); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedChecker.check(subject, evidence, Check.POST)); - function test_checkPost_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkPost_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedChecker.check(subject, evidence, Check.POST)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedChecker.check(subject, evidence, Check.POST)); - function test_checkerPre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE); + // function test_checkerPre_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE); - function test_checkerPre_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkerPre_whenCallerNotOwner_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.PRE)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.PRE)); - function test_checkerPre_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkerPre_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE)); - function test_checkerMain_whenCallerHasNoTokens_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkerMain_whenCallerHasNoTokens_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.MAIN)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.MAIN)); - function test_checkerMain_whenCallerHasTokens_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkerMain_whenCallerHasTokens_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.MAIN)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.MAIN)); - function test_checkerPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - rewardNft.mint(subject); + // function test_checkerPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.check(subject, evidence, Check.POST)); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.check(subject, evidence, Check.POST)); - function test_checkerPost_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_checkerPost_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.POST)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.POST)); - function test_internalPre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - advancedCheckerHarness.exposed__checkPre(subject, wrongEvidence); + // function test_internalPre_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); + // advancedCheckerHarness.exposed__checkPre(subject, wrongEvidence); - function test_internalPre_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_internalPre_whenCallerNotOwner_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.exposed__checkPre(notOwner, evidence)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.exposed__checkPre(notOwner, evidence)); - function test_internalPre_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_internalPre_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__checkPre(subject, evidence)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__checkPre(subject, evidence)); - function test_internalMain_whenCallerHasNoTokens_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_internalMain_whenCallerHasNoTokens_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.exposed__checkMain(notOwner, evidence)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.exposed__checkMain(notOwner, evidence)); - function test_internalMain_whenCallerHasTokens_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_internalMain_whenCallerHasTokens_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__checkMain(subject, evidence)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__checkMain(subject, evidence)); - function test_internalPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - rewardNft.mint(subject); + // function test_internalPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { + // vm.startPrank(target); - assert(!advancedCheckerHarness.exposed__checkPost(subject, evidence)); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // assert(!advancedCheckerHarness.exposed__checkPost(subject, evidence)); - function test_internalPost_whenValid_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - signupNft.mint(subject); + // function test_internalPost_whenValid_succeeds() public { + // vm.startPrank(target); - assert(advancedCheckerHarness.exposed__checkPost(subject, evidence)); + // signupNft.mint(subject); - vm.stopPrank(); - } + // assert(advancedCheckerHarness.exposed__checkPost(subject, evidence)); + + // vm.stopPrank(); + // } } contract AdvancedPolicy is Test { @@ -290,20 +306,17 @@ contract AdvancedPolicy is Test { NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; + BaseERC721CheckerFactory internal baseFactory; AdvancedERC721Checker internal advancedChecker; - AdvancedERC721Checker internal advancedCheckerSkipped; + AdvancedERC721CheckerFactory internal advancedFactory; AdvancedERC721Policy internal policy; - AdvancedERC721Policy internal policySkipped; - AdvancedERC721PolicyHarness internal policyHarness; - AdvancedERC721PolicyHarness internal policyHarnessSkipped; + AdvancedERC721PolicyFactory internal policyFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal baseVerifiers; - address[] internal advancedVerifiers; bytes[] public evidence = new bytes[](1); bytes[] public wrongEvidence = new bytes[](1); @@ -313,21 +326,28 @@ contract AdvancedPolicy is Test { signupNft = new NFT(); rewardNft = new NFT(); - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); + signupNft.mint(subject); + + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); + + address baseClone = baseFactory.createERC721Checker(address(signupNft)); + baseChecker = BaseERC721Checker(baseClone); - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); + address advancedClone = advancedFactory.createERC721Checker( + address(signupNft), + address(rewardNft), + address(baseChecker), + 1, + 0, + 10 + ); - 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); + advancedChecker = AdvancedERC721Checker(advancedClone); + + policyFactory = new AdvancedERC721PolicyFactory(); + address policyClone = policyFactory.createERC721Policy(address(advancedChecker), false, false, false); + policy = AdvancedERC721Policy(policyClone); evidence[0] = abi.encode(0); wrongEvidence[0] = abi.encode(1); @@ -335,687 +355,692 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_trait_returnsCorrectValue() public view { + function test_simple() public { + assertEq(policy.owner(), address(deployer)); assertEq(policy.trait(), "AdvancedERC721"); } - function test_setTarget_whenCallerNotOwner_reverts() public { - vm.startPrank(notOwner); + // function test_trait_returnsCorrectValue() public view { + // assertEq(policy.trait(), "AdvancedERC721"); + // } - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); - policy.setTarget(target); + // function test_setTarget_whenCallerNotOwner_reverts() public { + // vm.startPrank(notOwner); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); + // policy.setTarget(target); - function test_setTarget_whenZeroAddress_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); - policy.setTarget(address(0)); + // function test_setTarget_whenZeroAddress_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); + // policy.setTarget(address(0)); - function test_setTarget_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - vm.expectEmit(true, true, true, true); - emit TargetSet(target); + // function test_setTarget_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(target); + // vm.expectEmit(true, true, true, true); + // emit TargetSet(target); - vm.stopPrank(); - } + // policy.setTarget(target); - function test_setTarget_whenAlreadySet_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_setTarget_whenAlreadySet_reverts() public { + // vm.startPrank(deployer); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); - policy.setTarget(target); + // policy.setTarget(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); + // policy.setTarget(target); - function test_enforcePre_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_enforcePre_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policy.enforce(subject, evidence, Check.PRE); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policy.enforce(subject, evidence, Check.PRE); - function test_enforcePre_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_enforcePre_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policy.enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // policy.enforce(subject, evidence, Check.PRE); - function test_enforcePre_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policySkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforcePre_whenChecksSkipped_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policySkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); - policySkipped.enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); + // policySkipped.enforce(subject, evidence, Check.PRE); - function test_enforcePre_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePre_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policy.enforce(notOwner, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policy.enforce(notOwner, evidence, Check.PRE); - function test_enforcePre_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePre_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.PRE); + // vm.startPrank(target); - policy.enforce(subject, evidence, Check.PRE); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.PRE); - vm.stopPrank(); - } + // policy.enforce(subject, evidence, Check.PRE); - function test_enforcePre_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePre_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policy.enforce(subject, evidence, Check.PRE); + // policy.enforce(subject, evidence, Check.PRE); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policy.enforce(subject, evidence, Check.PRE); - function test_enforceMain_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_enforceMain_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policy.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policy.enforce(subject, evidence, Check.MAIN); - function test_enforceMain_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_enforceMain_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policy.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policy.enforce(subject, evidence, Check.MAIN); - function test_enforceMain_whenPreCheckMissing_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforceMain_whenPreCheckMissing_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); - policy.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); + // policy.enforce(subject, evidence, Check.MAIN); - function test_enforceMain_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforceMain_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.stopPrank(); - } + // policy.enforce(subject, evidence, Check.MAIN); - function test_enforceMain_whenMultipleValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforceMain_whenMultipleValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policy.enforce(subject, evidence, Check.MAIN); - policy.enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.stopPrank(); - } + // policy.enforce(subject, evidence, Check.MAIN); - function test_enforceMain_whenMultipleNotAllowed_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policySkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforceMain_whenMultipleNotAllowed_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policySkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policySkipped.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); - policySkipped.enforce(subject, evidence, Check.MAIN); + // policySkipped.enforce(subject, evidence, Check.MAIN); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); + // policySkipped.enforce(subject, evidence, Check.MAIN); - function test_enforcePost_whenPreCheckMissing_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePost_whenPreCheckMissing_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); - policy.enforce(subject, evidence, Check.PRE); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); - policy.enforce(subject, evidence, Check.POST); + // vm.startPrank(target); + // policy.enforce(subject, evidence, Check.PRE); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); + // policy.enforce(subject, evidence, Check.POST); - function test_enforcePost_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); + // function test_enforcePost_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policy.enforce(subject, evidence, Check.POST); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policy.enforce(subject, evidence, Check.POST); - function test_enforcePost_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policySkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforcePost_whenChecksSkipped_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policySkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policySkipped.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); - policySkipped.enforce(subject, evidence, Check.POST); + // policySkipped.enforce(subject, evidence, Check.MAIN); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); + // policySkipped.enforce(subject, evidence, Check.POST); - function test_enforcePost_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePost_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - rewardNft.mint(subject); + // policy.enforce(subject, evidence, Check.PRE); + // policy.enforce(subject, evidence, Check.MAIN); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policy.enforce(subject, evidence, Check.POST); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policy.enforce(subject, evidence, Check.POST); - function test_enforcePost_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePost_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.POST); + // policy.enforce(subject, evidence, Check.PRE); + // policy.enforce(subject, evidence, Check.MAIN); - policy.enforce(subject, evidence, Check.POST); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.POST); - vm.stopPrank(); - } + // policy.enforce(subject, evidence, Check.POST); - function test_enforcePost_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(target); - signupNft.mint(subject); + // function test_enforcePost_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policy.enforce(subject, evidence, Check.PRE); - policy.enforce(subject, evidence, Check.MAIN); - policy.enforce(subject, evidence, Check.POST); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policy.enforce(subject, evidence, Check.POST); + // policy.enforce(subject, evidence, Check.PRE); + // policy.enforce(subject, evidence, Check.MAIN); + // policy.enforce(subject, evidence, Check.POST); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policy.enforce(subject, evidence, Check.POST); - function test_enforcePreInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); + // function test_enforcePreInternal_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - function test_enforcePreInternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); + // function test_enforcePreInternal_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - function test_enforcePreInternal_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforcePreInternal_whenChecksSkipped_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarnessSkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); + // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.PRE); - function test_enforcePreInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePreInternal_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(notOwner, evidence, Check.PRE); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policyHarness.exposed__enforce(notOwner, evidence, Check.PRE); - function test_enforcePreInternal_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePreInternal_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.PRE); + // vm.startPrank(target); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.PRE); - vm.stopPrank(); - } + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - function test_enforcePreInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePreInternal_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - function test_enforceMainInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); + // function test_enforceMainInternal_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforceMainInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); + // function test_enforceMainInternal_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforceMainInternal_whenPreCheckMissing_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforceMainInternal_whenPreCheckMissing_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforceMainInternal_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforceMainInternal_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.stopPrank(); - } + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforceMainInternal_whenMultipleValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforceMainInternal_whenMultipleValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.MAIN); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.MAIN); - vm.stopPrank(); - } + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforceMainInternal_whenMultipleNotAllowed_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforceMainInternal_whenMultipleNotAllowed_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarnessSkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); + // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - function test_enforcePostInternal_whenPreCheckMissing_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePostInternal_whenPreCheckMissing_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // vm.startPrank(target); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.POST); - function test_enforcePostInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); + // function test_enforcePostInternal_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.POST); - function test_enforcePostInternal_whenChecksSkipped_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarnessSkipped.setTarget(target); - signupNft.mint(subject); + // function test_enforcePostInternal_whenChecksSkipped_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarnessSkipped.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); - policyHarnessSkipped.exposed__enforce(subject, evidence, Check.POST); + // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); + // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.POST); - function test_enforcePostInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePostInternal_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - rewardNft.mint(subject); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.POST); - function test_enforcePostInternal_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePostInternal_whenValid_succeeds() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence, Check.POST); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence, Check.POST); - vm.stopPrank(); - } + // policyHarness.exposed__enforce(subject, evidence, Check.POST); - function test_enforcePostInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policyHarness.setTarget(target); - signupNft.mint(subject); + // function test_enforcePostInternal_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policyHarness.setTarget(target); + // signupNft.mint(subject); - vm.startPrank(target); + // vm.stopPrank(); - policyHarness.exposed__enforce(subject, evidence, Check.PRE); - policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence, Check.POST); + // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + // policyHarness.exposed__enforce(subject, evidence, Check.POST); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policyHarness.exposed__enforce(subject, evidence, Check.POST); + + // vm.stopPrank(); + // } } contract Voting is Test { @@ -1026,16 +1051,20 @@ contract Voting is Test { NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; + BaseERC721CheckerFactory internal baseFactory; AdvancedERC721Checker internal advancedChecker; + AdvancedERC721CheckerFactory internal advancedFactory; AdvancedERC721Policy internal policy; + AdvancedERC721PolicyFactory internal policyFactory; AdvancedVoting internal voting; address public deployer = vm.addr(0x1); - address public subject = vm.addr(0x2); - address public notOwner = vm.addr(0x3); + address public target = vm.addr(0x2); + address public subject = vm.addr(0x3); + address public notOwner = vm.addr(0x4); - address[] internal baseVerifiers; - address[] internal advancedVerifiers; + bytes[] public evidence = new bytes[](1); + bytes[] public wrongEvidence = new bytes[](1); function setUp() public virtual { vm.startPrank(deployer); @@ -1043,276 +1072,307 @@ contract Voting is Test { signupNft = new NFT(); rewardNft = new NFT(); - baseVerifiers = new address[](1); - baseVerifiers[0] = address(signupNft); - baseChecker = new BaseERC721Checker(baseVerifiers); + baseFactory = new BaseERC721CheckerFactory(); + advancedFactory = new AdvancedERC721CheckerFactory(); + + address baseClone = baseFactory.createERC721Checker(address(signupNft)); + baseChecker = BaseERC721Checker(baseClone); + + address advancedClone = advancedFactory.createERC721Checker( + address(signupNft), + address(rewardNft), + address(baseChecker), + 1, + 0, + 10 + ); + + advancedChecker = AdvancedERC721Checker(advancedClone); + + policyFactory = new AdvancedERC721PolicyFactory(); + address policyClone = policyFactory.createERC721Policy(address(advancedChecker), false, false, false); + policy = AdvancedERC721Policy(policyClone); - advancedVerifiers = new address[](3); - advancedVerifiers[0] = address(signupNft); - advancedVerifiers[1] = address(rewardNft); - advancedVerifiers[2] = address(baseChecker); + evidence[0] = abi.encode(0); + wrongEvidence[0] = abi.encode(1); - advancedChecker = new AdvancedERC721Checker(advancedVerifiers, 1, 0, 10); - policy = new AdvancedERC721Policy(advancedChecker, false, false, true); voting = new AdvancedVoting(policy); vm.stopPrank(); } - function test_register_whenCallerNotTarget_reverts() public { + function test_simple() public { + assertEq(address(voting.POLICY()), address(policy)); + vm.startPrank(deployer); - policy.setTarget(deployer); + policy.setTarget(address(voting)); signupNft.mint(subject); vm.stopPrank(); - vm.startPrank(notOwner); + vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); voting.register(0); - vm.stopPrank(); } - function test_register_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // @todo refactoring - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_register_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(deployer); + // signupNft.mint(subject); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - voting.register(1); + // vm.startPrank(notOwner); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // voting.register(0); - function test_register_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_register_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(notOwner); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - voting.register(0); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); + // voting.register(1); - function test_register_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_register_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Registered(subject); + // vm.startPrank(notOwner); - voting.register(0); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // voting.register(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_register_whenAlreadyRegistered_reverts() public { - vm.startPrank(deployer); + // function test_register_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - signupNft.mint(subject); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - voting.register(0); + // vm.expectEmit(true, true, true, true); + // emit Registered(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - voting.register(0); + // voting.register(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_vote_whenNotRegistered_reverts() public { - vm.startPrank(deployer); + // function test_register_whenAlreadyRegistered_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - signupNft.mint(subject); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); - voting.vote(0); + // voting.register(0); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // voting.register(0); - function test_vote_whenInvalidOption_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_vote_whenNotRegistered_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(subject); - voting.register(0); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.InvalidOption.selector)); - voting.vote(3); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); + // voting.vote(0); - function test_vote_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_vote_whenInvalidOption_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(subject); - voting.register(0); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Voted(subject, 0); + // vm.startPrank(subject); + // voting.register(0); - voting.vote(0); + // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.InvalidOption.selector)); + // voting.vote(3); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_vote_whenMultipleValid_succeeds() public { - vm.startPrank(deployer); + // function test_vote_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - signupNft.mint(subject); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); + // voting.register(0); - voting.register(0); - voting.vote(0); + // vm.expectEmit(true, true, true, true); + // emit Voted(subject, 0); - vm.expectEmit(true, true, true, true); - emit Voted(subject, 0); - voting.vote(0); + // voting.vote(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_eligible_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // function test_vote_whenMultipleValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - signupNft.mint(subject); - signupNft.mint(notOwner); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(notOwner); + // vm.startPrank(subject); - voting.register(1); - voting.vote(0); + // voting.register(0); + // voting.vote(0); - vm.startPrank(subject); + // vm.expectEmit(true, true, true, true); + // emit Voted(subject, 0); + // voting.vote(0); - voting.register(0); - voting.vote(0); + // vm.stopPrank(); + // } - rewardNft.mint(subject); + // function test_eligible_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - voting.eligible(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); + // signupNft.mint(notOwner); - vm.stopPrank(); - } + // vm.stopPrank(); - function test_eligible_whenNotRegistered_reverts() public { - vm.startPrank(deployer); + // vm.startPrank(notOwner); - policy.setTarget(address(voting)); - signupNft.mint(subject); + // voting.register(1); + // voting.vote(0); - vm.stopPrank(); + // vm.startPrank(subject); - vm.startPrank(subject); + // voting.register(0); + // voting.vote(0); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); - voting.eligible(); + // rewardNft.mint(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // voting.eligible(); - function test_eligible_whenNotVoted_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_eligible_whenNotRegistered_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(subject); - voting.register(0); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotVoted.selector)); - voting.eligible(); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); + // voting.eligible(); - function test_eligible_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - signupNft.mint(subject); + // function test_eligible_whenNotVoted_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - vm.startPrank(subject); + // vm.stopPrank(); - voting.register(0); - voting.vote(0); + // vm.startPrank(subject); + // voting.register(0); - vm.expectEmit(true, true, true, true); - emit Eligible(subject); + // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotVoted.selector)); + // voting.eligible(); - voting.eligible(); + // vm.stopPrank(); + // } - vm.stopPrank(); - } + // function test_eligible_whenValid_succeeds() public { + // vm.startPrank(deployer); - function test_eligible_whenAlreadyEligible_reverts() public { - vm.startPrank(deployer); + // policy.setTarget(address(voting)); + // signupNft.mint(subject); - policy.setTarget(address(voting)); - signupNft.mint(subject); + // vm.stopPrank(); - vm.stopPrank(); + // vm.startPrank(subject); - vm.startPrank(subject); + // voting.register(0); + // voting.vote(0); - voting.register(0); - voting.vote(0); - voting.eligible(); + // vm.expectEmit(true, true, true, true); + // emit Eligible(subject); - vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.AlreadyEligible.selector)); - voting.eligible(); + // voting.eligible(); - vm.stopPrank(); - } + // vm.stopPrank(); + // } + + // function test_eligible_whenAlreadyEligible_reverts() public { + // vm.startPrank(deployer); + + // policy.setTarget(address(voting)); + // signupNft.mint(subject); + + // vm.stopPrank(); + + // vm.startPrank(subject); + + // voting.register(0); + // voting.vote(0); + // voting.eligible(); + + // 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 5f5bea5..fbb1720 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -4,19 +4,23 @@ 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 {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; +import {BaseERC721PolicyFactory} from "./base/BaseERC721PolicyFactory.sol"; import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; import {BaseVoting} from "./base/BaseVoting.sol"; -import {BaseERC721CheckerHarness} from "./wrappers/BaseERC721CheckerHarness.sol"; -import {BaseERC721PolicyHarness} from "./wrappers/BaseERC721PolicyHarness.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; import {IChecker} from "../core/interfaces/IChecker.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +// @todo add harnesses +// @todo add tests for factories + harnesses +// @todo add edge cases + contract BaseChecker is Test { NFT internal nft; BaseERC721Checker internal checker; - BaseERC721CheckerHarness internal checkerHarness; + BaseERC721CheckerFactory internal factory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); @@ -26,96 +30,102 @@ contract BaseChecker is Test { address[] internal verifiers; bytes[] public evidence = new bytes[](1); - function setUp() public virtual { + function setUp() public { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + nft.mint(subject); - checker = new BaseERC721Checker(verifiers); - checkerHarness = new BaseERC721CheckerHarness(verifiers); + factory = new BaseERC721CheckerFactory(); + address clone = factory.createERC721Checker(address(nft)); + checker = BaseERC721Checker(clone); evidence[0] = abi.encode(0); vm.stopPrank(); } - function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { - assertEq(checker.getVerifierAtIndex(0), address(nft)); + function test_simple() public { + bool result = checker.check(subject, evidence); + assertTrue(result, "Expected subject to own token 0"); } - function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - checker.getVerifierAtIndex(1); - } + /// @todo refactoring + // function test_getVerifierAtIndex_ReturnsCorrectAddress() public view { + // assertEq(checker.getVerifierAtIndex(0), address(nft)); + // } - function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { - assertEq(checkerHarness.exposed__getVerifierAtIndex(0), address(nft)); - } + // function test_getVerifierAtIndex_RevertWhen_VerifierNotFound() public { + // vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + // checker.getVerifierAtIndex(1); + // } - function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { - vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); - checkerHarness.exposed__getVerifierAtIndex(1); - } + // function test_getVerifierAtIndex_internal_ReturnsCorrectAddress() public view { + // assertEq(checkerHarness.exposed__getVerifierAtIndex(0), address(nft)); + // } - function test_checker_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + // function test_getVerifierAtIndex_internal_RevertWhen_VerifierNotFound() public { + // vm.expectRevert(abi.encodeWithSelector(IChecker.VerifierNotFound.selector)); + // checkerHarness.exposed__getVerifierAtIndex(1); + // } - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - checkerHarness.exposed__check(subject, evidence); + // function test_checker_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // checkerHarness.exposed__check(subject, evidence); - function test_checker_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - nft.mint(subject); + // function test_checker_whenCallerNotOwner_returnsFalse() public { + // vm.startPrank(target); - assert(!checkerHarness.exposed__check(notOwner, evidence)); + // nft.mint(subject); - vm.stopPrank(); - } + // assert(!checkerHarness.exposed__check(notOwner, evidence)); - function test_checker_whenCallerIsOwner_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - nft.mint(subject); + // function test_checker_whenCallerIsOwner_succeeds() public { + // vm.startPrank(target); - assert(checkerHarness.exposed__check(subject, evidence)); + // nft.mint(subject); - vm.stopPrank(); - } + // assert(checkerHarness.exposed__check(subject, evidence)); - function test_checkerExternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - checker.check(subject, evidence); + // function test_checkerExternal_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(target); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // checker.check(subject, evidence); - function test_checkerExternal_whenCallerNotOwner_returnsFalse() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - nft.mint(subject); + // function test_checkerExternal_whenCallerNotOwner_returnsFalse() public { + // vm.startPrank(target); - assert(!checker.check(notOwner, evidence)); + // nft.mint(subject); - vm.stopPrank(); - } + // assert(!checker.check(notOwner, evidence)); - function test_checkerExternal_whenCallerIsOwner_succeeds() public { - vm.startPrank(target); + // vm.stopPrank(); + // } - nft.mint(subject); + // function test_checkerExternal_whenCallerIsOwner_succeeds() public { + // vm.startPrank(target); - assert(checker.check(subject, evidence)); + // nft.mint(subject); - vm.stopPrank(); - } + // assert(checker.check(subject, evidence)); + + // vm.stopPrank(); + // } } contract BasePolicy is Test { @@ -124,250 +134,258 @@ contract BasePolicy is Test { NFT internal nft; BaseERC721Checker internal checker; + BaseERC721CheckerFactory internal checkerFactory; BaseERC721Policy internal policy; - BaseERC721PolicyHarness internal policyHarness; + BaseERC721PolicyFactory internal policyFactory; address public deployer = vm.addr(0x1); address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - address[] internal verifiers; bytes[] public evidence = new bytes[](1); function setUp() public virtual { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + nft.mint(subject); + + checkerFactory = new BaseERC721CheckerFactory(); + address checkerClone = checkerFactory.createERC721Checker(address(nft)); + checker = BaseERC721Checker(checkerClone); - checker = new BaseERC721Checker(verifiers); - policy = new BaseERC721Policy(checker); - policyHarness = new BaseERC721PolicyHarness(checker); + policyFactory = new BaseERC721PolicyFactory(); + address policyClone = policyFactory.createERC721Policy(address(checker)); + policy = BaseERC721Policy(policyClone); evidence[0] = abi.encode(0); vm.stopPrank(); } - function test_trait_returnsCorrectValue() public view { + function test_simple() public { + assertEq(policy.owner(), address(deployer)); assertEq(policy.trait(), "BaseERC721"); } + // @todo refactoring + // function test_trait_returnsCorrectValue() public view { + // assertEq(policy.trait(), "BaseERC721"); + // } - function test_getTarget_returnsExpectedAddress() public { - vm.startPrank(deployer); + // function test_getTarget_returnsExpectedAddress() public { + // vm.startPrank(deployer); - policy.setTarget(target); + // policy.setTarget(target); - assertEq(policy.getTarget(), target); + // assertEq(policy.getTarget(), target); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_setTarget_whenCallerNotOwner_reverts() public { - vm.startPrank(notOwner); + // function test_setTarget_whenCallerNotOwner_reverts() public { + // vm.startPrank(notOwner); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); - policy.setTarget(target); + // vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); + // policy.setTarget(target); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_setTarget_whenZeroAddress_reverts() public { - vm.startPrank(deployer); + // function test_setTarget_whenZeroAddress_reverts() public { + // vm.startPrank(deployer); - vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); - policy.setTarget(address(0)); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); + // policy.setTarget(address(0)); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_setTarget_whenValidAddress_succeeds() public { - vm.startPrank(deployer); + // function test_setTarget_whenValidAddress_succeeds() public { + // vm.startPrank(deployer); - vm.expectEmit(true, true, true, true); - emit TargetSet(target); + // vm.expectEmit(true, true, true, true); + // emit TargetSet(target); - policy.setTarget(target); + // policy.setTarget(target); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_setTarget_whenAlreadySet_reverts() public { - vm.startPrank(deployer); + // function test_setTarget_whenAlreadySet_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(target); + // policy.setTarget(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); - policy.setTarget(target); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); + // policy.setTarget(target); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforce_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // function test_enforce_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(target); + // policy.setTarget(target); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policy.enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policy.enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforce_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // function test_enforce_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(target); + // policy.setTarget(target); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policy.enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // policy.enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforce_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // function test_enforce_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(target); - nft.mint(subject); + // policy.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policy.enforce(notOwner, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policy.enforce(notOwner, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforce_whenValid_succeeds() public { - vm.startPrank(deployer); + // function test_enforce_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(target); - nft.mint(subject); + // policy.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence); - policy.enforce(subject, evidence); + // policy.enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforce_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // function test_enforce_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(target); - nft.mint(subject); + // policy.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - policy.enforce(subject, evidence); + // policy.enforce(subject, evidence); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policy.enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policy.enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforceInternal_whenCallerNotTarget_reverts() public { - vm.startPrank(deployer); + // function test_enforceInternal_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - policyHarness.setTarget(target); + // policyHarness.setTarget(target); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - policyHarness.exposed__enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // policyHarness.exposed__enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforceInternal_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // function test_enforceInternal_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - policyHarness.setTarget(target); + // policyHarness.setTarget(target); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - policyHarness.exposed__enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + // policyHarness.exposed__enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforceInternal_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // function test_enforceInternal_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - policyHarness.setTarget(target); - nft.mint(subject); + // policyHarness.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - policyHarness.exposed__enforce(notOwner, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // policyHarness.exposed__enforce(notOwner, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforceInternal_whenValid_succeeds() public { - vm.startPrank(deployer); + // function test_enforceInternal_whenValid_succeeds() public { + // vm.startPrank(deployer); - policyHarness.setTarget(target); - nft.mint(subject); + // policyHarness.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - vm.expectEmit(true, true, true, true); - emit Enforced(subject, target, evidence); + // vm.expectEmit(true, true, true, true); + // emit Enforced(subject, target, evidence); - policyHarness.exposed__enforce(subject, evidence); + // policyHarness.exposed__enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_enforceInternal_whenAlreadyEnforced_reverts() public { - vm.startPrank(deployer); + // function test_enforceInternal_whenAlreadyEnforced_reverts() public { + // vm.startPrank(deployer); - policyHarness.setTarget(target); - nft.mint(subject); + // policyHarness.setTarget(target); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(target); + // vm.startPrank(target); - policyHarness.exposed__enforce(subject, evidence); + // policyHarness.exposed__enforce(subject, evidence); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - policyHarness.exposed__enforce(subject, evidence); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // policyHarness.exposed__enforce(subject, evidence); - vm.stopPrank(); - } + // vm.stopPrank(); + // } } contract Voting is Test { @@ -376,181 +394,204 @@ contract Voting is Test { NFT internal nft; BaseERC721Checker internal checker; + BaseERC721CheckerFactory internal checkerFactory; BaseERC721Policy internal policy; + BaseERC721PolicyFactory internal policyFactory; BaseVoting internal voting; address public deployer = vm.addr(0x1); address public subject = vm.addr(0x2); address public notOwner = vm.addr(0x3); - address[] internal verifiers; - function setUp() public virtual { vm.startPrank(deployer); nft = new NFT(); - verifiers = new address[](1); - verifiers[0] = address(nft); + nft.mint(subject); + + checkerFactory = new BaseERC721CheckerFactory(); + address checkerClone = checkerFactory.createERC721Checker(address(nft)); + checker = BaseERC721Checker(checkerClone); + + policyFactory = new BaseERC721PolicyFactory(); + address policyClone = policyFactory.createERC721Policy(address(checker)); + policy = BaseERC721Policy(policyClone); - checker = new BaseERC721Checker(verifiers); - policy = new BaseERC721Policy(checker); voting = new BaseVoting(policy); vm.stopPrank(); } - function test_register_whenCallerNotTarget_reverts() public { + function test_simple() public { + assertEq(address(voting.POLICY()), address(policy)); + vm.startPrank(deployer); - policy.setTarget(deployer); + policy.setTarget(address(voting)); nft.mint(subject); vm.stopPrank(); - vm.startPrank(notOwner); + vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); voting.register(0); - vm.stopPrank(); } - function test_register_whenTokenDoesNotExist_reverts() public { - vm.startPrank(deployer); + // @todo refactoring - policy.setTarget(address(voting)); - nft.mint(subject); + // function test_register_whenCallerNotTarget_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(deployer); + // nft.mint(subject); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - voting.register(1); + // vm.startPrank(notOwner); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + // voting.register(0); - function test_register_whenCheckFails_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - nft.mint(subject); + // function test_register_whenTokenDoesNotExist_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.startPrank(notOwner); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - voting.register(0); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); + // voting.register(1); - function test_register_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - nft.mint(subject); + // function test_register_whenCheckFails_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.startPrank(subject); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Registered(subject); + // vm.startPrank(notOwner); - voting.register(0); + // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + // voting.register(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_register_whenAlreadyRegistered_reverts() public { - vm.startPrank(deployer); + // function test_register_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - nft.mint(subject); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - voting.register(0); + // vm.expectEmit(true, true, true, true); + // emit Registered(subject); - vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - voting.register(0); + // voting.register(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_vote_whenNotRegistered_reverts() public { - vm.startPrank(deployer); + // function test_register_whenAlreadyRegistered_reverts() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - nft.mint(subject); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); - vm.expectRevert(abi.encodeWithSelector(BaseVoting.NotRegistered.selector)); - voting.vote(0); + // voting.register(0); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + // voting.register(0); - function test_vote_whenInvalidOption_reverts() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - nft.mint(subject); + // function test_vote_whenNotRegistered_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.startPrank(subject); - voting.register(0); + // vm.stopPrank(); - vm.expectRevert(abi.encodeWithSelector(BaseVoting.InvalidOption.selector)); - voting.vote(3); + // vm.startPrank(subject); - vm.stopPrank(); - } + // vm.expectRevert(abi.encodeWithSelector(BaseVoting.NotRegistered.selector)); + // voting.vote(0); - function test_vote_whenValid_succeeds() public { - vm.startPrank(deployer); + // vm.stopPrank(); + // } - policy.setTarget(address(voting)); - nft.mint(subject); + // function test_vote_whenInvalidOption_reverts() public { + // vm.startPrank(deployer); - vm.stopPrank(); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.startPrank(subject); - voting.register(0); + // vm.stopPrank(); - vm.expectEmit(true, true, true, true); - emit Voted(subject, 0); + // vm.startPrank(subject); + // voting.register(0); - voting.vote(0); + // vm.expectRevert(abi.encodeWithSelector(BaseVoting.InvalidOption.selector)); + // voting.vote(3); - vm.stopPrank(); - } + // vm.stopPrank(); + // } - function test_vote_whenAlreadyVoted_reverts() public { - vm.startPrank(deployer); + // function test_vote_whenValid_succeeds() public { + // vm.startPrank(deployer); - policy.setTarget(address(voting)); - nft.mint(subject); + // policy.setTarget(address(voting)); + // nft.mint(subject); - vm.stopPrank(); + // vm.stopPrank(); - vm.startPrank(subject); + // vm.startPrank(subject); + // voting.register(0); - voting.register(0); - voting.vote(0); + // vm.expectEmit(true, true, true, true); + // emit Voted(subject, 0); - vm.expectRevert(abi.encodeWithSelector(BaseVoting.AlreadyVoted.selector)); - voting.vote(0); + // voting.vote(0); - vm.stopPrank(); - } + // vm.stopPrank(); + // } + + // function test_vote_whenAlreadyVoted_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(BaseVoting.AlreadyVoted.selector)); + // voting.vote(0); + + // vm.stopPrank(); + // } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index b205802..5843869 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -17,34 +17,38 @@ contract AdvancedERC721Checker is AdvancedChecker { /// - 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; + IERC721 public signupNft; + IERC721 public rewardNft; + BaseERC721Checker public baseERC721Checker; - /// @notice Validation thresholds. - uint256 public immutable MIN_BALANCE; - uint256 public immutable MIN_TOKEN_ID; - uint256 public immutable MAX_TOKEN_ID; + uint256 public minBalance; + uint256 public minTokenId; + uint256 public maxTokenId; - /// @notice Initializes checker with verification chain. - /// @dev Orders of verifiers array is crucial: - /// [signupNFT, rewardNFT, baseChecker] - /// @param _verifiers Ordered array of verification contract addresses. - /// @param _minBalance Required signup token balance. - /// @param _minTokenId Lower bound for valid token IDs. - /// @param _maxTokenId Upper bound for valid token IDs. - constructor( - address[] memory _verifiers, - uint256 _minBalance, - uint256 _minTokenId, - uint256 _maxTokenId - ) AdvancedChecker(_verifiers) { - SIGNUP_NFT = IERC721(_getVerifierAtIndex(0)); - REWARD_NFT = IERC721(_getVerifierAtIndex(1)); - BASE_ERC721_CHECKER = BaseERC721Checker(_getVerifierAtIndex(2)); - MIN_BALANCE = _minBalance; - MIN_TOKEN_ID = _minTokenId; - MAX_TOKEN_ID = _maxTokenId; + function initialize() public virtual override { + // 1. Call super to handle `_initialized` check. + super.initialize(); + + // 2. Retrieve appended bytes from the clone. + bytes memory data = _getAppendedBytes(); + + // 3. Decode everything in one shot: + ( + address signupNftAddr, + address rewardNftAddr, + address baseCheckerAddr, + uint256 minBalance_, + uint256 minTokenId_, + uint256 maxTokenId_ + ) = abi.decode(data, (address, address, address, uint256, uint256, uint256)); + + // 4. Assign to storage variables. + signupNft = IERC721(signupNftAddr); + rewardNft = IERC721(rewardNftAddr); + baseERC721Checker = BaseERC721Checker(baseCheckerAddr); + minBalance = minBalance_; + minTokenId = minTokenId_; + maxTokenId = maxTokenId_; } /// @notice Pre-check: Validates initial NFT ownership. @@ -54,7 +58,7 @@ contract AdvancedERC721Checker is AdvancedChecker { /// @return Validation status from base checker. function _checkPre(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPre(subject, evidence); - return BASE_ERC721_CHECKER.check(subject, evidence); + return baseERC721Checker.check(subject, evidence); } /// @notice Main-check: Validates token balance requirements. @@ -64,7 +68,7 @@ contract AdvancedERC721Checker is AdvancedChecker { /// @return True if balance meets requirements. function _checkMain(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkMain(subject, evidence); - return SIGNUP_NFT.balanceOf(subject) >= MIN_BALANCE && SIGNUP_NFT.balanceOf(subject) <= MIN_BALANCE; + return signupNft.balanceOf(subject) >= minBalance && signupNft.balanceOf(subject) <= minBalance; } /// @notice Post-check: Validates reward eligibility. @@ -74,6 +78,6 @@ contract AdvancedERC721Checker is AdvancedChecker { /// @return True if subject eligible for rewards. function _checkPost(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPost(subject, evidence); - return REWARD_NFT.balanceOf(subject) == 0; + return rewardNft.balanceOf(subject) == 0; } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol new file mode 100644 index 0000000..2a1fd23 --- /dev/null +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + +contract AdvancedERC721CheckerFactory { + address public immutable erc721CheckerImplementation; + + constructor() { + // Deploy the master ERC721Checker implementation once. + erc721CheckerImplementation = address(new AdvancedERC721Checker()); + } + + function createERC721Checker( + address _nftAddress, + address _rewardNft, + address _baseERC721Checker, + uint256 _minBalance, + uint256 _minTokenId, + uint256 _maxTokenId + ) external returns (address clone) { + // 1. Encode the address for appending. + bytes memory data = abi.encode( + _nftAddress, + _rewardNft, + _baseERC721Checker, + _minBalance, + _minTokenId, + _maxTokenId + ); + + // 2. Deploy the clone with appended data. + clone = LibClone.clone(erc721CheckerImplementation, data); + + // 3. Call initialize() so the new clone stores `_nftAddress` in `nft`. + AdvancedERC721Checker(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol index 93bded0..996de44 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol @@ -8,14 +8,6 @@ import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; /// @notice Three-phase ERC721 validation policy. /// @dev Enforces multi-stage checks through AdvancedERC721Checker. contract AdvancedERC721Policy is AdvancedPolicy { - /// @notice Initializes with checker contract. - constructor( - AdvancedERC721Checker _checker, - bool _skipPre, - bool _skipPost, - bool _allowMultipleMain - ) AdvancedPolicy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} - /// @notice Returns policy identifier. function trait() external pure returns (string memory) { return "AdvancedERC721"; diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol new file mode 100644 index 0000000..2616e20 --- /dev/null +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {LibClone} from "solady/src/utils/LibClone.sol"; +import {AdvancedERC721Policy} from "./AdvancedERC721Policy.sol"; + +/** + * @title AdvancedERC721PolicyFactory + * @notice Example factory for deploying minimal proxies of `AdvancedERC721Policy`. + */ +contract AdvancedERC721PolicyFactory { + /// @notice Address of the "master" (implementation) policy. + address public immutable advancedERC721PolicyImplementation; + + constructor() { + // Deploy the logic contract once. + // Or set it externally if already deployed. + advancedERC721PolicyImplementation = address(new AdvancedERC721Policy()); + } + + /** + * @notice Deploys a new minimal proxy clone, passing in `_checkerAddr` for initialization. + * @param _checkerAddr The address of the BaseERC721Checker to use. + * @return clone The address of the newly deployed clone. + */ + function createERC721Policy( + address _checkerAddr, + bool _skipPre, + bool _skipPost, + bool _allowMultipleMain + ) external returns (address clone) { + // 1. Encode the checker address for appending. + bytes memory data = abi.encode(msg.sender, _checkerAddr, _skipPre, _skipPost, _allowMultipleMain); + + // 2. Deploy the clone with appended data. + clone = LibClone.clone(advancedERC721PolicyImplementation, data); + + // 3. Call `initialize()` so the clone sets up its owner (the factory) + checker reference. + AdvancedERC721Policy(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol index 0828bd0..3dee779 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.20; import {BaseChecker} from "../../core/checker/BaseChecker.sol"; +import {Checker} from "../../core/checker/Checker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title BaseERC721Checker. @@ -9,12 +10,20 @@ import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @dev Extends BaseChecker for NFT ownership verification. contract BaseERC721Checker is BaseChecker { /// @notice NFT contract reference. - IERC721 public immutable NFT; + IERC721 public nft; - /// @notice Initializes with ERC721 contract. - /// @param _verifiers Array of addresses for existing verification contracts. - constructor(address[] memory _verifiers) BaseChecker(_verifiers) { - NFT = IERC721(_getVerifierAtIndex(0)); + function initialize() public virtual override { + // 1. Call super to handle `_initialized` check. + super.initialize(); + + // 2. Retrieve appended bytes from the clone. + bytes memory data = _getAppendedBytes(); + + // 3. Decode as a single address. + address nftAddress = abi.decode(data, (address)); + + // 4. Store it in our storage variable. + nft = IERC721(nftAddress); } /// @notice Validates token ownership. @@ -24,6 +33,6 @@ contract BaseERC721Checker is BaseChecker { function _check(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._check(subject, evidence); uint256 tokenId = abi.decode(evidence[0], (uint256)); - return NFT.ownerOf(tokenId) == subject; + return nft.ownerOf(tokenId) == subject; } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol new file mode 100644 index 0000000..c261b94 --- /dev/null +++ b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseERC721Checker} from "./BaseERC721Checker.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + +contract BaseERC721CheckerFactory { + address public immutable erc721CheckerImplementation; + + constructor() { + // Deploy the master ERC721Checker implementation once. + erc721CheckerImplementation = address(new BaseERC721Checker()); + } + + function createERC721Checker(address _nftAddress) external returns (address clone) { + // 1. Encode the address for appending. + bytes memory data = abi.encode(_nftAddress); + + // 2. Deploy the clone with appended data. + clone = LibClone.clone(erc721CheckerImplementation, data); + + // 3. Call initialize() so the new clone stores `_nftAddress` in `nft`. + BaseERC721Checker(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol index 0aa3dc3..c6cc5a6 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol @@ -2,23 +2,16 @@ pragma solidity ^0.8.20; import {BasePolicy} from "../../core/policy/BasePolicy.sol"; -import {BaseERC721Checker} from "./BaseERC721Checker.sol"; -/// @title BaseERC721Policy. -/// @notice Policy enforcer for ERC721 token validation. -/// @dev Extends BasePolicy with NFT-specific checks. +/** + * @title BaseERC721Policy + * @notice Policy enforcer for ERC721 token validation, built on top of BasePolicy. + * @dev In a minimal proxy context, we remove the constructor arguments and use `initialize()`. + */ contract BaseERC721Policy is BasePolicy { - /// @notice Checker contract reference. - BaseERC721Checker public immutable CHECKER; - - /// @notice Initializes with checker contract. - /// @param _checker Checker contract address. - constructor(BaseERC721Checker _checker) BasePolicy(_checker) { - CHECKER = BaseERC721Checker(_checker); - } - - /// @notice Returns policy identifier. - /// @return Policy trait string. + /** + * @notice A sample policy identifier. + */ function trait() external pure returns (string memory) { return "BaseERC721"; } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol new file mode 100644 index 0000000..7ebe282 --- /dev/null +++ b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {LibClone} from "solady/src/utils/LibClone.sol"; +import {BaseERC721Policy} from "./BaseERC721Policy.sol"; + +/** + * @title BaseERC721PolicyFactory + * @notice Example factory for deploying minimal proxies of `BaseERC721Policy`. + */ +contract BaseERC721PolicyFactory { + /// @notice Address of the "master" (implementation) policy. + address public immutable baseERC721PolicyImplementation; + + constructor() { + // Deploy the logic contract once. + // Or set it externally if already deployed. + baseERC721PolicyImplementation = address(new BaseERC721Policy()); + } + + /** + * @notice Deploys a new minimal proxy clone, passing in `_checkerAddr` for initialization. + * @param _checkerAddr The address of the BaseERC721Checker to use. + * @return clone The address of the newly deployed clone. + */ + function createERC721Policy(address _checkerAddr) external returns (address clone) { + // 1. Encode the checker address for appending. + bytes memory data = abi.encode(msg.sender, _checkerAddr); + + // 2. Deploy the clone with appended data. + clone = LibClone.clone(baseERC721PolicyImplementation, data); + + // 3. Call `initialize()` so the clone sets up its owner (the factory) + checker reference. + BaseERC721Policy(clone).initialize(); + } +} diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol index 6537b43..5ffb4e7 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.20; import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; - +// @todo refactoring /// @title AdvancedERC721CheckerHarness. /// @notice Test harness exposing internal methods of AdvancedERC721Checker. /// @dev Inherits AdvancedERC721Checker and exposes protected methods for testing. @@ -13,51 +13,46 @@ contract AdvancedERC721CheckerHarness is AdvancedERC721Checker { /// @param _minBalance Minimum token balance required. /// @param _minTokenId Minimum valid token ID. /// @param _maxTokenId Maximum valid token ID. - constructor( - address[] memory _verifiers, - uint256 _minBalance, - uint256 _minTokenId, - uint256 _maxTokenId - ) AdvancedERC721Checker(_verifiers, _minBalance, _minTokenId, _maxTokenId) {} - + // constructor( + // address[] memory _verifiers, + // uint256 _minBalance, + // uint256 _minTokenId, + // uint256 _maxTokenId + // ) AdvancedERC721Checker(_verifiers, _minBalance, _minTokenId, _maxTokenId) {} /// @notice Test exposure for _check method. /// @param subject Address to validate. /// @param evidence Validation data. /// @param checkType Type of check to perform. /// @return Validation result. - function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { - return _check(subject, evidence, checkType); - } - + // function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { + // return _check(subject, evidence, checkType); + // } /// @notice Test exposure for _checkPre method. /// @param subject Address to validate. /// @param evidence Validation data. /// @return Pre-check validation result. - function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPre(subject, evidence); - } - + // function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { + // return _checkPre(subject, evidence); + // } /// @notice Test exposure for _checkMain method. /// @param subject Address to validate. /// @param evidence Validation data. /// @return Main validation result. - function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkMain(subject, evidence); - } - + // function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { + // return _checkMain(subject, evidence); + // } /// @notice Test exposure for _checkPost method. /// @param subject Address to validate. /// @param evidence Validation data. /// @return Post-check validation result. - function exposed__checkPost(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPost(subject, evidence); - } - + // 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); - } + // function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { + // return _getVerifierAtIndex(index); + // } } diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol index d77bd66..b93a530 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol @@ -5,22 +5,22 @@ import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol"; import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; +// @todo refactoring /// @title AdvancedERC721PolicyHarness. /// @notice Test harness for AdvancedERC721Policy internal methods. contract AdvancedERC721PolicyHarness is AdvancedERC721Policy { /// @notice Initializes test harness. - constructor( - AdvancedERC721Checker _checker, - bool _skipPre, - bool _skipPost, - bool _allowMultipleMain - ) AdvancedERC721Policy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} - + // constructor( + // AdvancedERC721Checker _checker, + // bool _skipPre, + // bool _skipPost, + // bool _allowMultipleMain + // ) AdvancedERC721Policy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} /// @notice Test exposure for _enforce method. /// @param subject Address to validate. /// @param evidence Validation data. /// @param checkType Check type to enforce. - function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { - _enforce(subject, evidence, checkType); - } + // function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { + // _enforce(subject, evidence, checkType); + // } } diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol index f5458bb..67e64bc 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol @@ -3,26 +3,25 @@ pragma solidity ^0.8.20; import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; +// @todo refactoring /// @title BaseERC721CheckerHarness. /// @notice Test harness for BaseERC721Checker internal methods. contract BaseERC721CheckerHarness is BaseERC721Checker { /// @notice Initializes test harness with NFT contract. /// @param _verifiers Array of addresses for existing verification contracts. - constructor(address[] memory _verifiers) BaseERC721Checker(_verifiers) {} - + // constructor(address[] memory _verifiers) BaseERC721Checker(_verifiers) {} /// @notice Test exposure for _check method. /// @param subject Address to validate. /// @param evidence Validation data. /// @return Validation result. - function exposed__check(address subject, bytes[] calldata evidence) public view returns (bool) { - return _check(subject, evidence); - } - + // 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); - } + // function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { + // return _getVerifierAtIndex(index); + // } } diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol index 26c047b..be79160 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol @@ -4,16 +4,16 @@ pragma solidity ^0.8.20; import {BaseERC721Policy} from "../base/BaseERC721Policy.sol"; import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; +// @todo refactoring /// @title BaseERC721PolicyHarness. /// @notice Test harness for BaseERC721Policy internal methods. contract BaseERC721PolicyHarness is BaseERC721Policy { /// @notice Initializes test harness with checker. - constructor(BaseERC721Checker _checker) BaseERC721Policy(_checker) {} - + // constructor(BaseERC721Checker _checker) BaseERC721Policy(_checker) {} /// @notice Test exposure for _enforce method. /// @param subject Address to validate. /// @param evidence Validation data. - function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { - _enforce(subject, evidence); - } + // function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { + // _enforce(subject, evidence); + // } } diff --git a/packages/contracts/test/Advanced.test.ts b/packages/contracts/test/Advanced.test.ts index 690935a..5470d7b 100644 --- a/packages/contracts/test/Advanced.test.ts +++ b/packages/contracts/test/Advanced.test.ts @@ -1,1504 +1,1483 @@ -import { expect } from "chai" -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, - AdvancedERC721CheckerHarness__factory, - AdvancedERC721Policy, - AdvancedERC721Policy__factory, - AdvancedERC721PolicyHarness, - AdvancedERC721PolicyHarness__factory, - AdvancedVoting, - AdvancedVoting__factory, - IERC721Errors, - NFT, - NFT__factory -} from "../typechain-types" - -describe("Advanced", () => { - describe("Checker", () => { - async function deployAdvancedCheckerFixture() { - const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721CheckerHarnessFactory: AdvancedERC721CheckerHarness__factory = - await ethers.getContractFactory("AdvancedERC721CheckerHarness") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], - 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 signupNft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - - return { - signupNft, - rewardNft, - baseChecker, - advancedChecker, - advancedCheckerHarness, - deployer, - target, - subject, - subjectAddress, - notOwnerAddress, - validNFTId, - invalidNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) - - expect(advancedChecker).to.not.eq(undefined) - }) - }) - - describe("check", () => { - describe("pre check", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await expect( - advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") - }) - - it("returns false when not owner", async () => { - const { advancedChecker, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal( - false - ) - }) - - it("succeeds when valid", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal( - true - ) - }) - }) - describe("main check", () => { - it("returns false when balance insufficient", async () => { - const { advancedChecker, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal( - false - ) - }) - - it("succeeds when balance sufficient", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal( - true - ) - }) - }) - describe("post check", () => { - it("reverts when already rewarded", async () => { - const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await rewardNft.mint(subjectAddress) - - expect(await advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 2)).to.be.equal( - false - ) - }) - - it("succeeds when in valid range", async () => { - const { advancedChecker, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 2)).to.be.equal( - true - ) - }) - }) - }) - - describe("getVerifierAtIndex", () => { - it("returns correct verifier address", async () => { - const { advancedChecker, signupNft } = await loadFixture(deployAdvancedCheckerFixture) - expect(await advancedChecker.getVerifierAtIndex(0)).to.equal(await signupNft.getAddress()) - }) - - it("reverts when index out of bounds", async () => { - const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) - await expect(advancedChecker.getVerifierAtIndex(5)).to.be.revertedWithCustomError( - 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 { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await expect( - advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) - ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") - }) - - it("returns false when not owner", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) - ).to.be.equal(false) - }) - - it("succeeds when valid", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) - ).to.be.equal(true) - }) - }) - describe("main check", () => { - it("returns false when balance insufficient", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) - ).to.be.equal(false) - }) - - it("succeeds when balance sufficient", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) - ).to.be.equal(true) - }) - }) - describe("post check", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await rewardNft.mint(subjectAddress) - - expect( - await advancedCheckerHarness.connect(target).check(subjectAddress, [invalidNFTId], 2) - ).to.be.equal(false) - }) - - it("succeeds when in valid range", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) - ).to.be.equal(true) - }) - }) - }) - - describe("internal checkPre", () => { - it("reverts when evidence invalid", async () => { - const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await expect( - advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") - }) - - it("returns false when not owner", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when valid", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) - - describe("internal checkMain", () => { - it("returns false when balance insufficient", async () => { - const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when balance sufficient", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) - - describe("internal checkPost", () => { - it("reverts when evidence invalid", async () => { - const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - await rewardNft.mint(subjectAddress) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) - ).to.be.equal(false) - }) - - it("succeeds when in valid range", async () => { - const { advancedCheckerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedCheckerFixture) - - expect( - await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) - ).to.be.equal(true) - }) - }) - }) - - describe("Policy", () => { - async function deployAdvancedPolicyFixture() { - const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedERC721PolicyHarnessFactory: AdvancedERC721PolicyHarness__factory = - await ethers.getContractFactory("AdvancedERC721PolicyHarness") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( - "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 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 advancedChecker.getAddress(), - false, - false, - true - ) - const policySkipped: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await advancedCheckerSkippedPrePostNoMultMain.getAddress(), - true, - true, - false - ) - const policyHarness: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( - deployer - ).deploy(await advancedChecker.getAddress(), false, false, true) - const policyHarnessSkipped: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( - deployer - ).deploy(await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false) - - // mint 0 for subject. - await signupNft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - - return { - signupIERC721Errors, - rewardIERC721Errors, - AdvancedERC721PolicyFactory, - signupNft, - rewardNft, - advancedChecker, - advancedCheckerSkippedPrePostNoMultMain, - policyHarness, - policyHarnessSkipped, - policy, - policySkipped, - subject, - deployer, - target, - notOwner, - subjectAddress, - notOwnerAddress, - validEncodedNFTId, - invalidEncodedNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { policy } = await loadFixture(deployAdvancedPolicyFixture) - - expect(policy).to.not.eq(undefined) - }) - }) - - describe("trait", () => { - it("returns correct value", async () => { - const { policy } = await loadFixture(deployAdvancedPolicyFixture) - - expect(await policy.trait()).to.be.eq("AdvancedERC721") - }) - }) - - describe("setTarget", () => { - it("reverts when caller not owner", async () => { - const { policy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) - - await expect( - policy.connect(notOwner).setTarget(await target.getAddress()) - ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") - }) - - it("reverts when zero address", async () => { - const { policy, deployer } = await loadFixture(deployAdvancedPolicyFixture) - - await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( - policy, - "ZeroAddress" - ) - }) - - it("sets target correctly", async () => { - const { policy, target, AdvancedERC721PolicyFactory } = await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - const tx = await policy.setTarget(targetAddress) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - target: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.target).to.eq(targetAddress) - expect(await policy.getTarget()).to.eq(targetAddress) - }) - - it("reverts when already set", async () => { - const { policy, target } = await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(targetAddress) - - await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") - }) - }) - - describe("enforce", () => { - describe("pre check", () => { - it("reverts when caller not target", async () => { - const { policy, subject, target, subjectAddress } = await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - - await expect( - policy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) - ).to.be.revertedWithCustomError(policy, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - 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(rewardIERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when pre-check skipped", async () => { - const { policySkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policySkipped.setTarget(await target.getAddress()) - - await expect( - policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policySkipped, "CannotPreCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policy, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - - expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") - }) - - it("enforces pre-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(targetAddress) - - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - checkType: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(event.args.checkType).to.eq(0) - expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) - }) - - it("reverts when pre already enforced", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - - await expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") - }) - }) - - describe("main check", () => { - it("reverts when pre-check missing", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - - expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") - }) - - it("reverts when check unsuccessful", async () => { - const { policy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") - }) - - it("enforces main-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - checkType: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1) - }) - - it("executes multiple mains when allowed", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - await policy.setTarget(targetAddress) - - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - checkType: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(event.args.checkType).to.eq(1) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2) - }) - - it("executes multiple mains when allowed", async () => { - const { policySkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policySkipped.setTarget(await target.getAddress()) - await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policySkipped.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policySkipped, "MainCheckAlreadyEnforced") - }) - }) - - describe("post check", () => { - it("reverts when pre/main missing", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - - expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") - - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "MainCheckNotEnforced") - }) - - it("reverts when caller not target", async () => { - const { policy, subject, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) - ).to.be.revertedWithCustomError(policy, "TargetOnly") - }) - - it("reverts when already rewarded", async () => { - const { rewardNft, policy, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - await rewardNft.mint(subjectAddress) - - await expect( - policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") - }) - - it("reverts when post-check skipped", async () => { - const { policySkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policySkipped.setTarget(await target.getAddress()) - await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policySkipped, "CannotPostCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") - }) - - it("enforces post-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(targetAddress) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - checkType: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(event.args.checkType).to.eq(2) - expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) - }) - - it("reverts when post already enforced", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policy.setTarget(await target.getAddress()) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - - await expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") - }) - }) - }) - - describe("internal enforce", () => { - describe("internal pre", () => { - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 0) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { rewardIERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId], 0) - ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when pre-check skipped", async () => { - const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - - await expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPreCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces pre-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(targetAddress) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(subjectAddress))[0]).to.be.equal(true) - }) - - it("reverts when pre already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") - }) - }) - - describe("_main", () => { - it("reverts when pre-check missing", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces main-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(1) - }) - - it("executes multiple mains when allowed", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - await policyHarness.setTarget(targetAddress) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(2) - }) - - it("executes multiple mains when allowed", async () => { - const { policyHarnessSkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policyHarnessSkipped.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") - expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") - }) - }) - - describe("_post", () => { - it("reverts when pre/main missing", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - - expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "MainCheckNotEnforced") - }) - - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 2) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { rewardNft, policyHarness, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await rewardNft.mint(subjectAddress) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("reverts when post-check skipped", async () => { - const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarnessSkipped.setTarget(await target.getAddress()) - await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - await expect( - policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPostCheckWhenSkipped") - }) - - it("reverts when check unsuccessful", async () => { - const { policyHarness, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces post-check successfully", async () => { - const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(targetAddress) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - - const tx = await policyHarness - .connect(target) - .exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - const receipt = await tx.wait() - const event = AdvancedERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect((await policyHarness.enforced(subjectAddress))[2]).to.be.equal(true) - }) - - it("reverts when post already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployAdvancedPolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - - await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") - }) - }) - }) - }) - - describe("Voting", () => { - async function deployAdvancedVotingFixture() { - const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( - "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 advancedChecker.getAddress(), - false, - false, - true - ) - - const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( - await policy.getAddress() - ) - - // mint 0 for subject. - await signupNft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validNFTId = 0 - const invalidNFTId = 1 - const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) - const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) - - return { - signupIERC721Errors, - rewardIERC721Errors, - AdvancedVotingFactory, - AdvancedERC721PolicyFactory, - signupNft, - rewardNft, - advancedChecker, - voting, - policy, - subject, - deployer, - notOwner, - subjectAddress, - notOwnerAddress, - validNFTId, - invalidNFTId, - validEncodedNFTId, - invalidEncodedNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { voting } = await loadFixture(deployAdvancedVotingFixture) - - expect(voting).to.not.eq(undefined) - }) - }) - - describe("register", () => { - it("reverts when caller not target", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await notOwner.getAddress()) - - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "TargetOnly" - ) - }) - - it("reverts when evidence invalid", async () => { - 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( - signupIERC721Errors, - "ERC721NonexistentToken" - ) - }) - - it("reverts when check fails", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) - - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "UnsuccessfulCheck" - ) - }) - - it("registers successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = - await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - - const tx = await voting.connect(subject).register(validNFTId) - const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( - receipt?.logs[1] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - voter: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(0n) - expect(await voting.voteCounts(0)).to.be.equal(0) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - - it("reverts when already registered", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - - await voting.connect(subject).register(validNFTId) - - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "AlreadyEnforced" - ) - }) - }) - - describe("vote", () => { - it("reverts when not registered", async () => { - const { voting, policy, subject } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) - - await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") - }) - - it("reverts when option invalid", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - - await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") - }) - - it("votes successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedVotingFixture) - const option = 0 - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - - const tx = await voting.connect(subject).vote(option) - const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( - receipt?.logs[1] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - voter: string - option: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect(event.args.option).to.eq(option) - expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - - it("allows multiple votes", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedVotingFixture) - const option = 0 - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(option) - - const tx = await voting.connect(subject).vote(option) - const receipt = await tx.wait() - const event = AdvancedVotingFactory.interface.parseLog( - receipt?.logs[1] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - voter: string - option: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect(event.args.option).to.eq(option) - expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2n) - expect(await voting.voteCounts(0)).to.be.equal(2) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - }) - - describe("eligibility", () => { - it("reverts when caller not target", async () => { - const { voting, policy, subject, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await notOwner.getAddress()) - - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "TargetOnly" - ) - }) - - 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 rewardNft.mint(subject) - - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( - policy, - "UnsuccessfulCheck" - ) - }) - - it("reverts when check fails", async () => { - const { signupNft, rewardNft, deployer, voting, policy, notOwner, subject, validNFTId } = - await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) - 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 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 } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await notOwner.getAddress()) - - await expect(voting.connect(notOwner).eligible()).to.be.revertedWithCustomError(voting, "NotRegistered") - }) - - it("reverts when not voted", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError(voting, "NotVoted") - }) - - it("verifies eligibility successfully", async () => { - const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = - await loadFixture(deployAdvancedVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - await voting.connect(subject).vote(0) - - 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 } - ) as unknown as { - args: { - voter: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) - expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) - expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - - 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).eligible() - - await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( - voting, - "AlreadyEligible" - ) - }) - }) - describe("end to end", () => { - it("completes full voting lifecycle", async () => { - const [deployer]: Signer[] = await ethers.getSigners() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = - await ethers.getContractFactory("AdvancedERC721Checker") - const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = - await ethers.getContractFactory("AdvancedERC721Policy") - const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - - const signupNft: NFT = await NFTFactory.deploy() - const rewardNft: NFT = await NFTFactory.deploy() - const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await signupNft.getAddress() - ]) - const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect( - deployer - ).deploy( - [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], - 1, - 0, - 10 - ) - - const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( - await advancedChecker.getAddress(), - false, - false, - true - ) - - const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( - await policy.getAddress() - ) - - // set the target. - const targetAddress = await voting.getAddress() - await policy.setTarget(targetAddress) - - for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { - const voterAddress = await voter.getAddress() - - // mint for voter. - await signupNft.connect(deployer).mint(voterAddress) - - // register. - await voting.connect(voter).register(tokenId) - - // vote. - await voting.connect(voter).vote(tokenId % 2) - - // reward. - await voting.connect(voter).eligible() - - expect((await policy.enforced(voterAddress))[0]).to.be.equal(true) - expect((await policy.enforced(voterAddress))[1]).to.be.equal(1) - expect((await policy.enforced(voterAddress))[2]).to.be.equal(true) - } - }) - }) - }) -}) +// @todo refactoring +// describe("Advanced", () => { +// describe("Checker", () => { +// async function deployAdvancedCheckerFixture() { +// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = +// await ethers.getContractFactory("AdvancedERC721Checker") +// const AdvancedERC721CheckerHarnessFactory: AdvancedERC721CheckerHarness__factory = +// await ethers.getContractFactory("AdvancedERC721CheckerHarness") + +// const signupNft: NFT = await NFTFactory.deploy() +// const rewardNft: NFT = await NFTFactory.deploy() +// const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await signupNft.getAddress() +// ]) +// const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( +// [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], +// 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 signupNft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) +// const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + +// return { +// signupNft, +// rewardNft, +// baseChecker, +// advancedChecker, +// advancedCheckerHarness, +// deployer, +// target, +// subject, +// subjectAddress, +// notOwnerAddress, +// validNFTId, +// invalidNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) + +// expect(advancedChecker).to.not.eq(undefined) +// }) +// }) + +// describe("check", () => { +// describe("pre check", () => { +// it("reverts when evidence invalid", async () => { +// const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await expect( +// advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 0) +// ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") +// }) + +// it("returns false when not owner", async () => { +// const { advancedChecker, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal( +// false +// ) +// }) + +// it("succeeds when valid", async () => { +// const { advancedChecker, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal( +// true +// ) +// }) +// }) +// describe("main check", () => { +// it("returns false when balance insufficient", async () => { +// const { advancedChecker, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal( +// false +// ) +// }) + +// it("succeeds when balance sufficient", async () => { +// const { advancedChecker, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal( +// true +// ) +// }) +// }) +// describe("post check", () => { +// it("reverts when already rewarded", async () => { +// const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await rewardNft.mint(subjectAddress) + +// expect(await advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 2)).to.be.equal( +// false +// ) +// }) + +// it("succeeds when in valid range", async () => { +// const { advancedChecker, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 2)).to.be.equal( +// true +// ) +// }) +// }) +// }) + +// describe("getVerifierAtIndex", () => { +// it("returns correct verifier address", async () => { +// const { advancedChecker, signupNft } = await loadFixture(deployAdvancedCheckerFixture) +// expect(await advancedChecker.getVerifierAtIndex(0)).to.equal(await signupNft.getAddress()) +// }) + +// it("reverts when index out of bounds", async () => { +// const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) +// await expect(advancedChecker.getVerifierAtIndex(5)).to.be.revertedWithCustomError( +// 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 { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await expect( +// advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) +// ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") +// }) + +// it("returns false when not owner", async () => { +// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) +// ).to.be.equal(false) +// }) + +// it("succeeds when valid", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) +// ).to.be.equal(true) +// }) +// }) +// describe("main check", () => { +// it("returns false when balance insufficient", async () => { +// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) +// ).to.be.equal(false) +// }) + +// it("succeeds when balance sufficient", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) +// ).to.be.equal(true) +// }) +// }) +// describe("post check", () => { +// it("reverts when evidence invalid", async () => { +// const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await rewardNft.mint(subjectAddress) + +// expect( +// await advancedCheckerHarness.connect(target).check(subjectAddress, [invalidNFTId], 2) +// ).to.be.equal(false) +// }) + +// it("succeeds when in valid range", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) +// ).to.be.equal(true) +// }) +// }) +// }) + +// describe("internal checkPre", () => { +// it("reverts when evidence invalid", async () => { +// const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await expect( +// advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) +// ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") +// }) + +// it("returns false when not owner", async () => { +// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) +// ).to.be.equal(false) +// }) + +// it("succeeds when valid", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) +// ).to.be.equal(true) +// }) +// }) + +// describe("internal checkMain", () => { +// it("returns false when balance insufficient", async () => { +// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) +// ).to.be.equal(false) +// }) + +// it("succeeds when balance sufficient", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) +// ).to.be.equal(true) +// }) +// }) + +// describe("internal checkPost", () => { +// it("reverts when evidence invalid", async () => { +// const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// await rewardNft.mint(subjectAddress) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) +// ).to.be.equal(false) +// }) + +// it("succeeds when in valid range", async () => { +// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedCheckerFixture) + +// expect( +// await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) +// ).to.be.equal(true) +// }) +// }) +// }) + +// describe("Policy", () => { +// async function deployAdvancedPolicyFixture() { +// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = +// await ethers.getContractFactory("AdvancedERC721Checker") +// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = +// await ethers.getContractFactory("AdvancedERC721Policy") +// const AdvancedERC721PolicyHarnessFactory: AdvancedERC721PolicyHarness__factory = +// await ethers.getContractFactory("AdvancedERC721PolicyHarness") + +// const signupNft: NFT = await NFTFactory.deploy() +// const rewardNft: NFT = await NFTFactory.deploy() +// const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( +// "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 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 advancedChecker.getAddress(), +// false, +// false, +// true +// ) +// const policySkipped: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( +// await advancedCheckerSkippedPrePostNoMultMain.getAddress(), +// true, +// true, +// false +// ) +// const policyHarness: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( +// deployer +// ).deploy(await advancedChecker.getAddress(), false, false, true) +// const policyHarnessSkipped: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( +// deployer +// ).deploy(await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false) + +// // mint 0 for subject. +// await signupNft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) +// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + +// return { +// signupIERC721Errors, +// rewardIERC721Errors, +// AdvancedERC721PolicyFactory, +// signupNft, +// rewardNft, +// advancedChecker, +// advancedCheckerSkippedPrePostNoMultMain, +// policyHarness, +// policyHarnessSkipped, +// policy, +// policySkipped, +// subject, +// deployer, +// target, +// notOwner, +// subjectAddress, +// notOwnerAddress, +// validEncodedNFTId, +// invalidEncodedNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { policy } = await loadFixture(deployAdvancedPolicyFixture) + +// expect(policy).to.not.eq(undefined) +// }) +// }) + +// describe("trait", () => { +// it("returns correct value", async () => { +// const { policy } = await loadFixture(deployAdvancedPolicyFixture) + +// expect(await policy.trait()).to.be.eq("AdvancedERC721") +// }) +// }) + +// describe("setTarget", () => { +// it("reverts when caller not owner", async () => { +// const { policy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) + +// await expect( +// policy.connect(notOwner).setTarget(await target.getAddress()) +// ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") +// }) + +// it("reverts when zero address", async () => { +// const { policy, deployer } = await loadFixture(deployAdvancedPolicyFixture) + +// await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( +// policy, +// "ZeroAddress" +// ) +// }) + +// it("sets target correctly", async () => { +// const { policy, target, AdvancedERC721PolicyFactory } = await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// const tx = await policy.setTarget(targetAddress) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// target: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.target).to.eq(targetAddress) +// expect(await policy.getTarget()).to.eq(targetAddress) +// }) + +// it("reverts when already set", async () => { +// const { policy, target } = await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(targetAddress) + +// await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") +// }) +// }) + +// describe("enforce", () => { +// describe("pre check", () => { +// it("reverts when caller not target", async () => { +// const { policy, subject, target, subjectAddress } = await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// await expect( +// policy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) +// ).to.be.revertedWithCustomError(policy, "TargetOnly") +// }) + +// it("reverts when evidence invalid", async () => { +// 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(rewardIERC721Errors, "ERC721NonexistentToken") +// }) + +// it("reverts when pre-check skipped", async () => { +// const { policySkipped, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policySkipped.setTarget(await target.getAddress()) + +// await expect( +// policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policySkipped, "CannotPreCheckWhenSkipped") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policy, target, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// expect( +// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") +// }) + +// it("enforces pre-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(targetAddress) + +// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// checkType: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(event.args.checkType).to.eq(0) +// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) +// }) + +// it("reverts when pre already enforced", async () => { +// const { policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + +// await expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") +// }) +// }) + +// describe("main check", () => { +// it("reverts when pre-check missing", async () => { +// const { policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + +// expect( +// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") +// }) + +// it("enforces main-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + +// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// checkType: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(event.args.checkType).to.eq(1) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1) +// }) + +// it("executes multiple mains when allowed", async () => { +// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() +// await policy.setTarget(targetAddress) + +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// checkType: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(event.args.checkType).to.eq(1) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2) +// }) + +// it("executes multiple mains when allowed", async () => { +// const { policySkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policySkipped.setTarget(await target.getAddress()) +// await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// expect( +// policySkipped.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policySkipped, "MainCheckAlreadyEnforced") +// }) +// }) + +// describe("post check", () => { +// it("reverts when pre/main missing", async () => { +// const { policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") + +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + +// expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policy, "MainCheckNotEnforced") +// }) + +// it("reverts when caller not target", async () => { +// const { policy, subject, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// await expect( +// policy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) +// ).to.be.revertedWithCustomError(policy, "TargetOnly") +// }) + +// it("reverts when already rewarded", async () => { +// const { rewardNft, policy, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// await rewardNft.mint(subjectAddress) + +// await expect( +// policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") +// }) + +// it("reverts when post-check skipped", async () => { +// const { policySkipped, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policySkipped.setTarget(await target.getAddress()) +// await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// await expect( +// policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policySkipped, "CannotPostCheckWhenSkipped") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// expect( +// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") +// }) + +// it("enforces post-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(targetAddress) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + +// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// checkType: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(event.args.checkType).to.eq(2) +// expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) +// }) + +// it("reverts when post already enforced", async () => { +// const { policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policy.setTarget(await target.getAddress()) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + +// await expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") +// }) +// }) +// }) + +// describe("internal enforce", () => { +// describe("internal pre", () => { +// it("reverts when caller not target", async () => { +// const { policyHarness, subject, target, subjectAddress } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await expect( +// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 0) +// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") +// }) + +// it("reverts when evidence invalid", async () => { +// const { rewardIERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") +// }) + +// it("reverts when pre-check skipped", async () => { +// const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarnessSkipped.setTarget(await target.getAddress()) + +// await expect( +// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPreCheckWhenSkipped") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// expect( +// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") +// }) + +// it("enforces pre-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policyHarness.setTarget(targetAddress) + +// const tx = await policyHarness +// .connect(target) +// .exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect((await policyHarness.enforced(subjectAddress))[0]).to.be.equal(true) +// }) + +// it("reverts when pre already enforced", async () => { +// const { policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) + +// await expect( +// policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) +// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") +// }) +// }) + +// describe("_main", () => { +// it("reverts when pre-check missing", async () => { +// const { policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policyHarness, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) + +// expect( +// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") +// }) + +// it("enforces main-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) + +// const tx = await policyHarness +// .connect(target) +// .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(1) +// }) + +// it("executes multiple mains when allowed", async () => { +// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() +// await policyHarness.setTarget(targetAddress) + +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// const tx = await policyHarness +// .connect(target) +// .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(2) +// }) + +// it("executes multiple mains when allowed", async () => { +// const { policyHarnessSkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarnessSkipped.setTarget(await target.getAddress()) +// await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// expect( +// policyHarnessSkipped.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") +// expect( +// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) +// ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") +// }) +// }) + +// describe("_post", () => { +// it("reverts when pre/main missing", async () => { +// const { policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") + +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) + +// expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarness, "MainCheckNotEnforced") +// }) + +// it("reverts when caller not target", async () => { +// const { policyHarness, subject, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// await expect( +// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 2) +// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") +// }) + +// it("reverts when evidence invalid", async () => { +// const { rewardNft, policyHarness, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// await rewardNft.mint(subjectAddress) + +// await expect( +// policyHarness.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") +// }) + +// it("reverts when post-check skipped", async () => { +// const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarnessSkipped.setTarget(await target.getAddress()) +// await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// await expect( +// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPostCheckWhenSkipped") +// }) + +// it("reverts when check unsuccessful", async () => { +// const { policyHarness, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// expect( +// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") +// }) + +// it("enforces post-check successfully", async () => { +// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) +// const targetAddress = await target.getAddress() + +// await policyHarness.setTarget(targetAddress) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) + +// const tx = await policyHarness +// .connect(target) +// .exposed__enforce(subjectAddress, [validEncodedNFTId], 2) +// const receipt = await tx.wait() +// const event = AdvancedERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect((await policyHarness.enforced(subjectAddress))[2]).to.be.equal(true) +// }) + +// it("reverts when post already enforced", async () => { +// const { policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployAdvancedPolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) + +// await expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) +// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") +// }) +// }) +// }) +// }) + +// describe("Voting", () => { +// async function deployAdvancedVotingFixture() { +// const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = +// await ethers.getContractFactory("AdvancedERC721Checker") +// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = +// await ethers.getContractFactory("AdvancedERC721Policy") +// const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + +// const signupNft: NFT = await NFTFactory.deploy() +// const rewardNft: NFT = await NFTFactory.deploy() +// const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( +// "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 advancedChecker.getAddress(), +// false, +// false, +// true +// ) + +// const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( +// await policy.getAddress() +// ) + +// // mint 0 for subject. +// await signupNft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validNFTId = 0 +// const invalidNFTId = 1 +// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) +// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) + +// return { +// signupIERC721Errors, +// rewardIERC721Errors, +// AdvancedVotingFactory, +// AdvancedERC721PolicyFactory, +// signupNft, +// rewardNft, +// advancedChecker, +// voting, +// policy, +// subject, +// deployer, +// notOwner, +// subjectAddress, +// notOwnerAddress, +// validNFTId, +// invalidNFTId, +// validEncodedNFTId, +// invalidEncodedNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { voting } = await loadFixture(deployAdvancedVotingFixture) + +// expect(voting).to.not.eq(undefined) +// }) +// }) + +// describe("register", () => { +// it("reverts when caller not target", async () => { +// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await notOwner.getAddress()) + +// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "TargetOnly" +// ) +// }) + +// it("reverts when evidence invalid", async () => { +// 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( +// signupIERC721Errors, +// "ERC721NonexistentToken" +// ) +// }) + +// it("reverts when check fails", async () => { +// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await voting.getAddress()) + +// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "UnsuccessfulCheck" +// ) +// }) + +// it("registers successfully", async () => { +// const { AdvancedVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = +// await loadFixture(deployAdvancedVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) + +// const tx = await voting.connect(subject).register(validNFTId) +// const receipt = await tx.wait() +// const event = AdvancedVotingFactory.interface.parseLog( +// receipt?.logs[1] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// voter: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(0n) +// expect(await voting.voteCounts(0)).to.be.equal(0) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) + +// it("reverts when already registered", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) + +// await voting.connect(subject).register(validNFTId) + +// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "AlreadyEnforced" +// ) +// }) +// }) + +// describe("vote", () => { +// it("reverts when not registered", async () => { +// const { voting, policy, subject } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await voting.getAddress()) + +// await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") +// }) + +// it("reverts when option invalid", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await voting.getAddress()) +// await voting.connect(subject).register(validNFTId) + +// await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") +// }) + +// it("votes successfully", async () => { +// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedVotingFixture) +// const option = 0 +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) +// await voting.connect(subject).register(validNFTId) + +// const tx = await voting.connect(subject).vote(option) +// const receipt = await tx.wait() +// const event = AdvancedVotingFactory.interface.parseLog( +// receipt?.logs[1] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// voter: string +// option: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect(event.args.option).to.eq(option) +// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) +// expect(await voting.voteCounts(0)).to.be.equal(1) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) + +// it("allows multiple votes", async () => { +// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedVotingFixture) +// const option = 0 +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) +// await voting.connect(subject).register(validNFTId) +// await voting.connect(subject).vote(option) + +// const tx = await voting.connect(subject).vote(option) +// const receipt = await tx.wait() +// const event = AdvancedVotingFactory.interface.parseLog( +// receipt?.logs[1] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// voter: string +// option: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect(event.args.option).to.eq(option) +// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2n) +// expect(await voting.voteCounts(0)).to.be.equal(2) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) +// }) + +// describe("eligibility", () => { +// it("reverts when caller not target", async () => { +// const { voting, policy, subject, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await notOwner.getAddress()) + +// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "TargetOnly" +// ) +// }) + +// 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 rewardNft.mint(subject) + +// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( +// policy, +// "UnsuccessfulCheck" +// ) +// }) + +// it("reverts when check fails", async () => { +// const { signupNft, rewardNft, deployer, voting, policy, notOwner, subject, validNFTId } = +// await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await voting.getAddress()) +// 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 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 } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await notOwner.getAddress()) + +// await expect(voting.connect(notOwner).eligible()).to.be.revertedWithCustomError(voting, "NotRegistered") +// }) + +// it("reverts when not voted", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) + +// await policy.setTarget(await voting.getAddress()) +// await voting.connect(subject).register(validNFTId) + +// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError(voting, "NotVoted") +// }) + +// it("verifies eligibility successfully", async () => { +// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = +// await loadFixture(deployAdvancedVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) +// await voting.connect(subject).register(validNFTId) +// await voting.connect(subject).vote(0) + +// 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 } +// ) as unknown as { +// args: { +// voter: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) +// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) +// expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) +// expect(await voting.voteCounts(0)).to.be.equal(1) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) + +// 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).eligible() + +// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( +// voting, +// "AlreadyEligible" +// ) +// }) +// }) +// describe("end to end", () => { +// it("completes full voting lifecycle", async () => { +// const [deployer]: Signer[] = await ethers.getSigners() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = +// await ethers.getContractFactory("AdvancedERC721Checker") +// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = +// await ethers.getContractFactory("AdvancedERC721Policy") +// const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + +// const signupNft: NFT = await NFTFactory.deploy() +// const rewardNft: NFT = await NFTFactory.deploy() +// const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await signupNft.getAddress() +// ]) +// const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect( +// deployer +// ).deploy( +// [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], +// 1, +// 0, +// 10 +// ) + +// const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( +// await advancedChecker.getAddress(), +// false, +// false, +// true +// ) + +// const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( +// await policy.getAddress() +// ) + +// // set the target. +// const targetAddress = await voting.getAddress() +// await policy.setTarget(targetAddress) + +// for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { +// const voterAddress = await voter.getAddress() + +// // mint for voter. +// await signupNft.connect(deployer).mint(voterAddress) + +// // register. +// await voting.connect(voter).register(tokenId) + +// // vote. +// await voting.connect(voter).vote(tokenId % 2) + +// // reward. +// await voting.connect(voter).eligible() + +// expect((await policy.enforced(voterAddress))[0]).to.be.equal(true) +// expect((await policy.enforced(voterAddress))[1]).to.be.equal(1) +// expect((await policy.enforced(voterAddress))[2]).to.be.equal(true) +// } +// }) +// }) +// }) +// }) diff --git a/packages/contracts/test/Base.test.ts b/packages/contracts/test/Base.test.ts index 0bf5b52..5241158 100644 --- a/packages/contracts/test/Base.test.ts +++ b/packages/contracts/test/Base.test.ts @@ -1,643 +1,625 @@ -import { expect } from "chai" -import { ethers } from "hardhat" -import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" -import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" -import { - BaseERC721Checker, - BaseERC721Checker__factory, - BaseERC721Policy, - BaseERC721Policy__factory, - BaseERC721CheckerHarness, - BaseERC721CheckerHarness__factory, - BaseERC721PolicyHarness, - BaseERC721PolicyHarness__factory, - NFT, - NFT__factory, - IERC721Errors, - BaseVoting__factory, - BaseVoting -} from "../typechain-types" - -describe("Base", () => { - describe("Checker", () => { - async function deployBaseCheckerFixture() { - const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721CheckerHarnessFactory: BaseERC721CheckerHarness__factory = - await ethers.getContractFactory("BaseERC721CheckerHarness") - - const nft: NFT = await NFTFactory.deploy() - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const checkerHarness: BaseERC721CheckerHarness = await BaseERC721CheckerHarnessFactory.connect( - deployer - ).deploy([await nft.getAddress()]) - - // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - - return { - nft, - checker, - checkerHarness, - target, - subjectAddress, - notOwnerAddress, - validNFTId, - invalidNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { checker } = await loadFixture(deployBaseCheckerFixture) - - expect(checker).to.not.eq(undefined) - }) - }) - - 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 } = - await loadFixture(deployBaseCheckerFixture) - - await expect( - checker.connect(target).check(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) - - it("returns false when subject not owner", async () => { - const { checker, target, notOwnerAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) - - expect(await checker.connect(target).check(notOwnerAddress, [validNFTId])).to.be.equal(false) - }) - - it("succeeds when valid", async () => { - const { checker, target, subjectAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) - - expect(await checker.connect(target).check(subjectAddress, [validNFTId])).to.be.equal(true) - }) - }) - - describe("internal check", () => { - it("reverts when evidence is invalid", async () => { - const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = - await loadFixture(deployBaseCheckerFixture) - - await expect( - checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId]) - ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") - }) - - it("returns false when subject not owner", async () => { - const { checkerHarness, target, notOwnerAddress, validNFTId } = - await loadFixture(deployBaseCheckerFixture) - - expect(await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId])).to.be.equal( - false - ) - }) - - it("succeeds when valid", async () => { - const { checkerHarness, target, subjectAddress, validNFTId } = - await loadFixture(deployBaseCheckerFixture) - - expect(await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId])).to.be.equal( - true - ) - }) - }) - }) - - describe("Policy", () => { - async function deployBasePolicyFixture() { - const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseERC721PolicyHarnessFactory: BaseERC721PolicyHarness__factory = - await ethers.getContractFactory("BaseERC721PolicyHarness") - - const nft: NFT = await NFTFactory.deploy() - const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() - ) - const policyHarness: BaseERC721PolicyHarness = await BaseERC721PolicyHarnessFactory.connect( - deployer - ).deploy(await checker.getAddress()) - - // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) - const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - - return { - iERC721Errors, - BaseERC721PolicyFactory, - nft, - policyHarness, - policy, - subject, - deployer, - target, - notOwner, - subjectAddress, - notOwnerAddress, - validEncodedNFTId, - invalidEncodedNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { policy } = await loadFixture(deployBasePolicyFixture) - - expect(policy).to.not.eq(undefined) - }) - }) - - describe("trait", () => { - it("returns correct value", async () => { - const { policy } = await loadFixture(deployBasePolicyFixture) - - expect(await policy.trait()).to.be.eq("BaseERC721") - }) - }) - - describe("setTarget", () => { - it("reverts when caller not owner", async () => { - const { policy, notOwner, target } = await loadFixture(deployBasePolicyFixture) - - await expect( - policy.connect(notOwner).setTarget(await target.getAddress()) - ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") - }) - - it("reverts when zero address", async () => { - const { policy, deployer } = await loadFixture(deployBasePolicyFixture) - - await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( - policy, - "ZeroAddress" - ) - }) - - it("sets target correctly", async () => { - const { policy, target, BaseERC721PolicyFactory } = await loadFixture(deployBasePolicyFixture) - const targetAddress = await target.getAddress() - - const tx = await policy.setTarget(targetAddress) - const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - target: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.target).to.eq(targetAddress) - expect(await policy.getTarget()).to.eq(targetAddress) - }) - - it("reverts when already set", async () => { - const { policy, target } = await loadFixture(deployBasePolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(targetAddress) - - await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") - }) - }) - - describe("enforce", () => { - it("reverts when caller not target", async () => { - const { policy, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) - - await policy.setTarget(await target.getAddress()) - - await expect(policy.connect(subject).enforce(subjectAddress, [ZeroHash])).to.be.revertedWithCustomError( - policy, - "TargetOnly" - ) - }) - - it("reverts when evidence invalid", async () => { - const { iERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policy.setTarget(await target.getAddress()) - - await expect( - policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId]) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when check fails", async () => { - const { policy, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policy.setTarget(await target.getAddress()) - - expect( - policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") - }) - - it("enforces successfully", async () => { - const { BaseERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - const targetAddress = await target.getAddress() - - await policy.setTarget(await target.getAddress()) - - const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policy.enforced(subjectAddress)).to.be.equal(true) - }) - - it("reverts when already enforced", async () => { - const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) - - await policy.setTarget(await target.getAddress()) - - await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - - await expect( - policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") - }) - }) - - describe("internal enforce", () => { - it("reverts when caller not target", async () => { - const { policyHarness, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash]) - ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") - }) - - it("reverts when evidence invalid", async () => { - const { iERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await expect( - policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId]) - ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") - }) - - it("reverts when check fails", async () => { - const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - expect( - policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") - }) - - it("enforces successfully", async () => { - const { BaseERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - const targetAddress = await target.getAddress() - - await policyHarness.setTarget(await target.getAddress()) - - const tx = await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) - const receipt = await tx.wait() - const event = BaseERC721PolicyFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - subject: string - target: string - evidence: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.subject).to.eq(subjectAddress) - expect(event.args.target).to.eq(targetAddress) - expect(event.args.evidence[0]).to.eq(validEncodedNFTId) - expect(await policyHarness.enforced(subjectAddress)).to.be.equal(true) - }) - - it("reverts when already enforced", async () => { - const { policyHarness, target, subjectAddress, validEncodedNFTId } = - await loadFixture(deployBasePolicyFixture) - - await policyHarness.setTarget(await target.getAddress()) - - await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) - - await expect( - policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") - }) - }) - }) - - describe("Voting", () => { - async function deployBaseVotingFixture() { - const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() - const subjectAddress: string = await subject.getAddress() - const notOwnerAddress: string = await notOwner.getAddress() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") - - const nft: NFT = await NFTFactory.deploy() - const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() - ) - const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) - - // mint 0 for subject. - await nft.connect(deployer).mint(subjectAddress) - - // encoded token ids. - const validNFTId = 0 - const invalidNFTId = 1 - const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) - const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) - - return { - iERC721Errors, - BaseVotingFactory, - nft, - voting, - policy, - subject, - deployer, - notOwner, - subjectAddress, - notOwnerAddress, - validNFTId, - invalidNFTId, - validEncodedNFTId, - invalidEncodedNFTId - } - } - - describe("constructor", () => { - it("deploys correctly", async () => { - const { voting } = await loadFixture(deployBaseVotingFixture) - - expect(voting).to.not.eq(undefined) - }) - }) - - describe("register", () => { - it("reverts when caller not target", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) - - await policy.setTarget(await notOwner.getAddress()) - - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "TargetOnly" - ) - }) - - it("reverts when evidence invalid", async () => { - const { iERC721Errors, voting, policy, subject, invalidNFTId } = - await loadFixture(deployBaseVotingFixture) - - await policy.setTarget(await voting.getAddress()) - - await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( - iERC721Errors, - "ERC721NonexistentToken" - ) - }) - - it("reverts when check fails", async () => { - const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) - - await policy.setTarget(await voting.getAddress()) - - await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "UnsuccessfulCheck" - ) - }) - - it("registers successfully", async () => { - const { BaseVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = - await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - - const tx = await voting.connect(subject).register(validNFTId) - const receipt = await tx.wait() - const event = BaseVotingFactory.interface.parseLog( - receipt?.logs[1] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - voter: string - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect(await policy.enforced(subjectAddress)).to.be.equal(true) - expect(await voting.hasVoted(subjectAddress)).to.be.equal(false) - expect(await voting.voteCounts(0)).to.be.equal(0) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - - it("reverts when already registered", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - - await voting.connect(subject).register(validNFTId) - - await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( - policy, - "AlreadyEnforced" - ) - }) - }) - - describe("vote", () => { - it("reverts when not registered", async () => { - const { voting, policy, subject } = await loadFixture(deployBaseVotingFixture) - - await policy.setTarget(await voting.getAddress()) - - await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") - }) - - it("reverts when option invalid", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - - await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") - }) - - it("votes successfully", async () => { - const { BaseVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = - await loadFixture(deployBaseVotingFixture) - const option = 0 - - await policy.setTarget(await voting.getAddress()) - await voting.connect(subject).register(validNFTId) - - const tx = await voting.connect(subject).vote(option) - const receipt = await tx.wait() - const event = BaseVotingFactory.interface.parseLog( - receipt?.logs[0] as unknown as { topics: string[]; data: string } - ) as unknown as { - args: { - voter: string - option: number - } - } - - expect(receipt?.status).to.eq(1) - expect(event.args.voter).to.eq(subjectAddress) - expect(event.args.option).to.eq(option) - expect(await voting.hasVoted(subjectAddress)).to.be.equal(true) - expect(await voting.voteCounts(0)).to.be.equal(1) - expect(await voting.voteCounts(1)).to.be.equal(0) - }) - - it("reverts when already voted", async () => { - const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - const targetAddress = await voting.getAddress() - - await policy.setTarget(targetAddress) - await voting.connect(subject).register(validNFTId) - - await voting.connect(subject).vote(0) - - await expect(voting.connect(subject).vote(1)).to.be.revertedWithCustomError(voting, "AlreadyVoted") - }) - }) - - describe("end to end", () => { - it("completes full voting flow", async () => { - const [deployer]: Signer[] = await ethers.getSigners() - - const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") - const BaseERC721CheckerFactory: BaseERC721Checker__factory = - await ethers.getContractFactory("BaseERC721Checker") - const BaseERC721PolicyFactory: BaseERC721Policy__factory = - await ethers.getContractFactory("BaseERC721Policy") - const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") - - const nft: NFT = await NFTFactory.deploy() - - const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ - await nft.getAddress() - ]) - const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( - await checker.getAddress() - ) - const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) - - // set the target. - await policy.setTarget(await voting.getAddress()) - - for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { - const voterAddress = await voter.getAddress() - - // mint for voter. - await nft.connect(deployer).mint(voterAddress) - - // register. - await voting.connect(voter).register(tokenId) - - // vote. - await voting.connect(voter).vote(tokenId % 2) - - expect(await voting.hasVoted(voter)).to.be.equal(true) - } - }) - }) - }) -}) +// @todo refactoring + +// describe("Base", () => { +// describe("Checker", () => { +// async function deployBaseCheckerFixture() { +// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const BaseERC721CheckerHarnessFactory: BaseERC721CheckerHarness__factory = +// await ethers.getContractFactory("BaseERC721CheckerHarness") + +// const nft: NFT = await NFTFactory.deploy() +// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await nft.getAddress() +// ]) +// const checkerHarness: BaseERC721CheckerHarness = await BaseERC721CheckerHarnessFactory.connect( +// deployer +// ).deploy([await nft.getAddress()]) + +// // mint 0 for subject. +// await nft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) +// const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + +// return { +// nft, +// checker, +// checkerHarness, +// target, +// subjectAddress, +// notOwnerAddress, +// validNFTId, +// invalidNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { checker } = await loadFixture(deployBaseCheckerFixture) + +// expect(checker).to.not.eq(undefined) +// }) +// }) + +// 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 } = +// await loadFixture(deployBaseCheckerFixture) + +// await expect( +// checker.connect(target).check(subjectAddress, [invalidNFTId]) +// ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") +// }) + +// it("returns false when subject not owner", async () => { +// const { checker, target, notOwnerAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) + +// expect(await checker.connect(target).check(notOwnerAddress, [validNFTId])).to.be.equal(false) +// }) + +// it("succeeds when valid", async () => { +// const { checker, target, subjectAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) + +// expect(await checker.connect(target).check(subjectAddress, [validNFTId])).to.be.equal(true) +// }) +// }) + +// describe("internal check", () => { +// it("reverts when evidence is invalid", async () => { +// const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = +// await loadFixture(deployBaseCheckerFixture) + +// await expect( +// checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId]) +// ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") +// }) + +// it("returns false when subject not owner", async () => { +// const { checkerHarness, target, notOwnerAddress, validNFTId } = +// await loadFixture(deployBaseCheckerFixture) + +// expect(await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId])).to.be.equal( +// false +// ) +// }) + +// it("succeeds when valid", async () => { +// const { checkerHarness, target, subjectAddress, validNFTId } = +// await loadFixture(deployBaseCheckerFixture) + +// expect(await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId])).to.be.equal( +// true +// ) +// }) +// }) +// }) + +// describe("Policy", () => { +// async function deployBasePolicyFixture() { +// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const BaseERC721PolicyFactory: BaseERC721Policy__factory = +// await ethers.getContractFactory("BaseERC721Policy") +// const BaseERC721PolicyHarnessFactory: BaseERC721PolicyHarness__factory = +// await ethers.getContractFactory("BaseERC721PolicyHarness") + +// const nft: NFT = await NFTFactory.deploy() +// const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) + +// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await nft.getAddress() +// ]) +// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( +// await checker.getAddress() +// ) +// const policyHarness: BaseERC721PolicyHarness = await BaseERC721PolicyHarnessFactory.connect( +// deployer +// ).deploy(await checker.getAddress()) + +// // mint 0 for subject. +// await nft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) +// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + +// return { +// iERC721Errors, +// BaseERC721PolicyFactory, +// nft, +// policyHarness, +// policy, +// subject, +// deployer, +// target, +// notOwner, +// subjectAddress, +// notOwnerAddress, +// validEncodedNFTId, +// invalidEncodedNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { policy } = await loadFixture(deployBasePolicyFixture) + +// expect(policy).to.not.eq(undefined) +// }) +// }) + +// describe("trait", () => { +// it("returns correct value", async () => { +// const { policy } = await loadFixture(deployBasePolicyFixture) + +// expect(await policy.trait()).to.be.eq("BaseERC721") +// }) +// }) + +// describe("setTarget", () => { +// it("reverts when caller not owner", async () => { +// const { policy, notOwner, target } = await loadFixture(deployBasePolicyFixture) + +// await expect( +// policy.connect(notOwner).setTarget(await target.getAddress()) +// ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") +// }) + +// it("reverts when zero address", async () => { +// const { policy, deployer } = await loadFixture(deployBasePolicyFixture) + +// await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( +// policy, +// "ZeroAddress" +// ) +// }) + +// it("sets target correctly", async () => { +// const { policy, target, BaseERC721PolicyFactory } = await loadFixture(deployBasePolicyFixture) +// const targetAddress = await target.getAddress() + +// const tx = await policy.setTarget(targetAddress) +// const receipt = await tx.wait() +// const event = BaseERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// target: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.target).to.eq(targetAddress) +// expect(await policy.getTarget()).to.eq(targetAddress) +// }) + +// it("reverts when already set", async () => { +// const { policy, target } = await loadFixture(deployBasePolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(targetAddress) + +// await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") +// }) +// }) + +// describe("enforce", () => { +// it("reverts when caller not target", async () => { +// const { policy, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// await expect(policy.connect(subject).enforce(subjectAddress, [ZeroHash])).to.be.revertedWithCustomError( +// policy, +// "TargetOnly" +// ) +// }) + +// it("reverts when evidence invalid", async () => { +// const { iERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// await expect( +// policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId]) +// ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") +// }) + +// it("reverts when check fails", async () => { +// const { policy, target, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// expect( +// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId]) +// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") +// }) + +// it("enforces successfully", async () => { +// const { BaseERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) +// const targetAddress = await target.getAddress() + +// await policy.setTarget(await target.getAddress()) + +// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) +// const receipt = await tx.wait() +// const event = BaseERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(await policy.enforced(subjectAddress)).to.be.equal(true) +// }) + +// it("reverts when already enforced", async () => { +// const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) + +// await policy.setTarget(await target.getAddress()) + +// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) + +// await expect( +// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) +// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") +// }) +// }) + +// describe("internal enforce", () => { +// it("reverts when caller not target", async () => { +// const { policyHarness, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await expect( +// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash]) +// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") +// }) + +// it("reverts when evidence invalid", async () => { +// const { iERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await expect( +// policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId]) +// ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") +// }) + +// it("reverts when check fails", async () => { +// const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// expect( +// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId]) +// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") +// }) + +// it("enforces successfully", async () => { +// const { BaseERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) +// const targetAddress = await target.getAddress() + +// await policyHarness.setTarget(await target.getAddress()) + +// const tx = await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) +// const receipt = await tx.wait() +// const event = BaseERC721PolicyFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// subject: string +// target: string +// evidence: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.subject).to.eq(subjectAddress) +// expect(event.args.target).to.eq(targetAddress) +// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) +// expect(await policyHarness.enforced(subjectAddress)).to.be.equal(true) +// }) + +// it("reverts when already enforced", async () => { +// const { policyHarness, target, subjectAddress, validEncodedNFTId } = +// await loadFixture(deployBasePolicyFixture) + +// await policyHarness.setTarget(await target.getAddress()) + +// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) + +// await expect( +// policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId]) +// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") +// }) +// }) +// }) + +// describe("Voting", () => { +// async function deployBaseVotingFixture() { +// const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() +// const subjectAddress: string = await subject.getAddress() +// const notOwnerAddress: string = await notOwner.getAddress() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const BaseERC721PolicyFactory: BaseERC721Policy__factory = +// await ethers.getContractFactory("BaseERC721Policy") +// const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + +// const nft: NFT = await NFTFactory.deploy() +// const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) + +// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await nft.getAddress() +// ]) +// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( +// await checker.getAddress() +// ) +// const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) + +// // mint 0 for subject. +// await nft.connect(deployer).mint(subjectAddress) + +// // encoded token ids. +// const validNFTId = 0 +// const invalidNFTId = 1 +// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) +// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) + +// return { +// iERC721Errors, +// BaseVotingFactory, +// nft, +// voting, +// policy, +// subject, +// deployer, +// notOwner, +// subjectAddress, +// notOwnerAddress, +// validNFTId, +// invalidNFTId, +// validEncodedNFTId, +// invalidEncodedNFTId +// } +// } + +// describe("constructor", () => { +// it("deploys correctly", async () => { +// const { voting } = await loadFixture(deployBaseVotingFixture) + +// expect(voting).to.not.eq(undefined) +// }) +// }) + +// describe("register", () => { +// it("reverts when caller not target", async () => { +// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + +// await policy.setTarget(await notOwner.getAddress()) + +// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "TargetOnly" +// ) +// }) + +// it("reverts when evidence invalid", async () => { +// const { iERC721Errors, voting, policy, subject, invalidNFTId } = +// await loadFixture(deployBaseVotingFixture) + +// await policy.setTarget(await voting.getAddress()) + +// await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( +// iERC721Errors, +// "ERC721NonexistentToken" +// ) +// }) + +// it("reverts when check fails", async () => { +// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + +// await policy.setTarget(await voting.getAddress()) + +// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "UnsuccessfulCheck" +// ) +// }) + +// it("registers successfully", async () => { +// const { BaseVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = +// await loadFixture(deployBaseVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) + +// const tx = await voting.connect(subject).register(validNFTId) +// const receipt = await tx.wait() +// const event = BaseVotingFactory.interface.parseLog( +// receipt?.logs[1] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// voter: string +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect(await policy.enforced(subjectAddress)).to.be.equal(true) +// expect(await voting.hasVoted(subjectAddress)).to.be.equal(false) +// expect(await voting.voteCounts(0)).to.be.equal(0) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) + +// it("reverts when already registered", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) + +// await voting.connect(subject).register(validNFTId) + +// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( +// policy, +// "AlreadyEnforced" +// ) +// }) +// }) + +// describe("vote", () => { +// it("reverts when not registered", async () => { +// const { voting, policy, subject } = await loadFixture(deployBaseVotingFixture) + +// await policy.setTarget(await voting.getAddress()) + +// await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") +// }) + +// it("reverts when option invalid", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + +// await policy.setTarget(await voting.getAddress()) +// await voting.connect(subject).register(validNFTId) + +// await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") +// }) + +// it("votes successfully", async () => { +// const { BaseVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = +// await loadFixture(deployBaseVotingFixture) +// const option = 0 + +// await policy.setTarget(await voting.getAddress()) +// await voting.connect(subject).register(validNFTId) + +// const tx = await voting.connect(subject).vote(option) +// const receipt = await tx.wait() +// const event = BaseVotingFactory.interface.parseLog( +// receipt?.logs[0] as unknown as { topics: string[]; data: string } +// ) as unknown as { +// args: { +// voter: string +// option: number +// } +// } + +// expect(receipt?.status).to.eq(1) +// expect(event.args.voter).to.eq(subjectAddress) +// expect(event.args.option).to.eq(option) +// expect(await voting.hasVoted(subjectAddress)).to.be.equal(true) +// expect(await voting.voteCounts(0)).to.be.equal(1) +// expect(await voting.voteCounts(1)).to.be.equal(0) +// }) + +// it("reverts when already voted", async () => { +// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) +// const targetAddress = await voting.getAddress() + +// await policy.setTarget(targetAddress) +// await voting.connect(subject).register(validNFTId) + +// await voting.connect(subject).vote(0) + +// await expect(voting.connect(subject).vote(1)).to.be.revertedWithCustomError(voting, "AlreadyVoted") +// }) +// }) + +// describe("end to end", () => { +// it("completes full voting flow", async () => { +// const [deployer]: Signer[] = await ethers.getSigners() + +// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") +// const BaseERC721CheckerFactory: BaseERC721Checker__factory = +// await ethers.getContractFactory("BaseERC721Checker") +// const BaseERC721PolicyFactory: BaseERC721Policy__factory = +// await ethers.getContractFactory("BaseERC721Policy") +// const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + +// const nft: NFT = await NFTFactory.deploy() + +// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ +// await nft.getAddress() +// ]) +// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( +// await checker.getAddress() +// ) +// const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) + +// // set the target. +// await policy.setTarget(await voting.getAddress()) + +// for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { +// const voterAddress = await voter.getAddress() + +// // mint for voter. +// await nft.connect(deployer).mint(voterAddress) + +// // register. +// await voting.connect(voter).register(tokenId) + +// // vote. +// await voting.connect(voter).vote(tokenId % 2) + +// expect(await voting.hasVoted(voter)).to.be.equal(true) +// } +// }) +// }) +// }) +// }) From 4a3b1722db8e04092d5de70af1c4466a185994f0 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 28 Jan 2025 12:05:32 +0100 Subject: [PATCH 05/12] refactor(contracts): add common clone contract for proxies --- .../src/core/checker/AdvancedChecker.sol | 4 +-- .../src/core/checker/BaseChecker.sol | 4 +-- .../contracts/src/core/checker/Checker.sol | 21 --------------- .../contracts/src/core/interfaces/IClone.sol | 10 +++++++ .../contracts/src/core/interfaces/IPolicy.sol | 1 - .../src/core/policy/AdvancedPolicy.sol | 4 +-- .../contracts/src/core/policy/BasePolicy.sol | 4 +-- .../contracts/src/core/policy/Policy.sol | 20 +++----------- .../contracts/src/core/proxy/Clone.sol | 27 +++++++++++++++++++ .../test/advanced/AdvancedERC721Checker.sol | 4 +-- .../src/test/base/BaseERC721Checker.sol | 5 ++-- 11 files changed, 53 insertions(+), 51 deletions(-) delete mode 100644 packages/contracts/contracts/src/core/checker/Checker.sol create mode 100644 packages/contracts/contracts/src/core/interfaces/IClone.sol create mode 100644 packages/contracts/contracts/src/core/proxy/Clone.sol diff --git a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol index aa0c250..3843d2b 100644 --- a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol @@ -2,12 +2,12 @@ pragma solidity ^0.8.20; import {IAdvancedChecker, Check, CheckStatus} from "../interfaces/IAdvancedChecker.sol"; -import {Checker} from "./Checker.sol"; +import {Clone} from "../proxy/Clone.sol"; /// @title AdvancedChecker. /// @notice Multi-phase validation checker with pre, main, and post checks. /// @dev Base contract for implementing complex validation logic with configurable check phases. -abstract contract AdvancedChecker is Checker, IAdvancedChecker { +abstract contract AdvancedChecker is Clone, IAdvancedChecker { /// @notice Entry point for validation checks. /// @param subject Address to validate. /// @param evidence Validation data. diff --git a/packages/contracts/contracts/src/core/checker/BaseChecker.sol b/packages/contracts/contracts/src/core/checker/BaseChecker.sol index 00df3d9..26b4f75 100644 --- a/packages/contracts/contracts/src/core/checker/BaseChecker.sol +++ b/packages/contracts/contracts/src/core/checker/BaseChecker.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.20; import {IBaseChecker} from "../interfaces/IBaseChecker.sol"; -import {Checker} from "./Checker.sol"; +import {Clone} from "../proxy/Clone.sol"; /// @title BaseChecker /// @notice Abstract base contract for implementing validation checks. /// @dev Provides a standardized interface for implementing custom validation logic /// through the internal _check method. -abstract contract BaseChecker is Checker, IBaseChecker { +abstract contract BaseChecker is Clone, IBaseChecker { /// @notice Validates evidence for a given subject address. /// @dev External view function that delegates to internal _check implementation. /// @param subject Address to validate. diff --git a/packages/contracts/contracts/src/core/checker/Checker.sol b/packages/contracts/contracts/src/core/checker/Checker.sol deleted file mode 100644 index b6ab9a5..0000000 --- a/packages/contracts/contracts/src/core/checker/Checker.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {IChecker} from "../interfaces/IChecker.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; - -// @todo refactoring & comments -abstract contract Checker is IChecker { - bool private _initialized; - - error AlreadyInitialized(); - - function initialize() public virtual { - if (_initialized) revert AlreadyInitialized(); - _initialized = true; - } - - function _getAppendedBytes() internal view returns (bytes memory) { - return LibClone.argsOnClone(address(this)); - } -} diff --git a/packages/contracts/contracts/src/core/interfaces/IClone.sol b/packages/contracts/contracts/src/core/interfaces/IClone.sol new file mode 100644 index 0000000..8f9648d --- /dev/null +++ b/packages/contracts/contracts/src/core/interfaces/IClone.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface IClone { + error AlreadyInitialized(); + + function initialize() external; + + function getAppendedBytes() external returns (bytes memory); +} diff --git a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol index 4fed982..9541a64 100644 --- a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol +++ b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol @@ -14,7 +14,6 @@ interface IPolicy { error TargetOnly(); error TargetAlreadySet(); error AlreadyEnforced(); - error AlreadyInitialized(); /// @notice Returns policy trait identifier. /// @return Policy trait string (e.g., "Semaphore"). diff --git a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol index 8b59309..8cf5e18 100644 --- a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol +++ b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol @@ -29,9 +29,9 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { * @notice Initialize function for minimal proxy clones. * Decodes appended bytes for (AdvancedChecker, skipPre, skipPost, allowMultipleMain). */ - function initialize() public virtual override { + function _initialize() internal virtual override { // 1. Call Policy’s initialize to set ownership and `_initialized`. - super.initialize(); + super._initialize(); // 2. Decode the appended bytes for the advanced config. bytes memory data = _getAppendedBytes(); diff --git a/packages/contracts/contracts/src/core/policy/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol index e42dc07..51f8107 100644 --- a/packages/contracts/contracts/src/core/policy/BasePolicy.sol +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -22,9 +22,9 @@ abstract contract BasePolicy is Policy, IBasePolicy { * @notice Initializes the contract with a BaseChecker instance, reading from appended bytes. * Replaces the old constructor-based approach. */ - function initialize() public virtual override { + function _initialize() internal virtual override { // 1. Call the base `Policy.initialize()` to set ownership / handle `_initialized`. - super.initialize(); + super._initialize(); // 2. Decode the appended bytes to get the BaseChecker address (and anything else you might need). bytes memory data = _getAppendedBytes(); diff --git a/packages/contracts/contracts/src/core/policy/Policy.sol b/packages/contracts/contracts/src/core/policy/Policy.sol index 2d509bc..1bbc518 100644 --- a/packages/contracts/contracts/src/core/policy/Policy.sol +++ b/packages/contracts/contracts/src/core/policy/Policy.sol @@ -3,33 +3,21 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IPolicy} from "../interfaces/IPolicy.sol"; +import {Clone} from "../proxy/Clone.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; -abstract contract Policy is IPolicy, Ownable(msg.sender) { - /// @notice One-time initialization guard. - bool private _initialized; - +abstract contract Policy is Clone, IPolicy, Ownable(msg.sender) { /// @notice The “gatekeeped” contract address set once by the owner (if at all). address internal target; - /** - * @notice The base init. By default, transfers ownership to `msg.sender` (i.e., the caller). - * @dev If you want the factory to always be the owner, you just have the factory call this function, - * so `msg.sender` is the factory in that transaction. - */ - function initialize() public virtual { - if (_initialized) revert AlreadyInitialized(); - _initialized = true; + function _initialize() internal virtual override { + super._initialize(); // By default, set the owner to the caller (likely the factory). // this is not the zero address as above! _transferOwnership(msg.sender); } - function _getAppendedBytes() internal view returns (bytes memory) { - return LibClone.argsOnClone(address(this)); - } - /** * @notice Only the owner can call `setTarget` once. * @param _target The contract to be protected by this policy. diff --git a/packages/contracts/contracts/src/core/proxy/Clone.sol b/packages/contracts/contracts/src/core/proxy/Clone.sol new file mode 100644 index 0000000..a77124f --- /dev/null +++ b/packages/contracts/contracts/src/core/proxy/Clone.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IClone} from "../interfaces/IClone.sol"; +import {LibClone} from "solady/src/utils/LibClone.sol"; + +// @todo refactoring & comments +abstract contract Clone is IClone { + bool private _initialized; + + function initialize() external { + _initialize(); + } + + function getAppendedBytes() external returns (bytes memory) { + return _getAppendedBytes(); + } + + function _initialize() internal virtual { + if (_initialized) revert AlreadyInitialized(); + _initialized = true; + } + + function _getAppendedBytes() internal virtual returns (bytes memory) { + return LibClone.argsOnClone(address(this)); + } +} diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index 5843869..39e71c7 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -25,9 +25,9 @@ contract AdvancedERC721Checker is AdvancedChecker { uint256 public minTokenId; uint256 public maxTokenId; - function initialize() public virtual override { + function _initialize() internal override { // 1. Call super to handle `_initialized` check. - super.initialize(); + super._initialize(); // 2. Retrieve appended bytes from the clone. bytes memory data = _getAppendedBytes(); diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol index 3dee779..1d9c53e 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.20; import {BaseChecker} from "../../core/checker/BaseChecker.sol"; -import {Checker} from "../../core/checker/Checker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /// @title BaseERC721Checker. @@ -12,9 +11,9 @@ contract BaseERC721Checker is BaseChecker { /// @notice NFT contract reference. IERC721 public nft; - function initialize() public virtual override { + function _initialize() internal override { // 1. Call super to handle `_initialized` check. - super.initialize(); + super._initialize(); // 2. Retrieve appended bytes from the clone. bytes memory data = _getAppendedBytes(); From 79795a1980069fc1e7bb306ad9d0e6b973e22e5c Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 28 Jan 2025 14:03:46 +0100 Subject: [PATCH 06/12] refactor(contracts): add hierarchy of factory and clones --- .../src/core/interfaces/IFactory.sol | 9 +- .../contracts/src/core/proxy/Factory.sol | 20 +++++ .../contracts/src/test/Advanced.t.sol | 82 +++++++++++-------- .../contracts/contracts/src/test/Base.t.sol | 42 +++++++--- .../advanced/AdvancedERC721CheckerFactory.sol | 22 ++--- .../advanced/AdvancedERC721PolicyFactory.sol | 32 ++------ .../test/base/BaseERC721CheckerFactory.sol | 14 ++-- .../src/test/base/BaseERC721PolicyFactory.sol | 23 ++---- 8 files changed, 131 insertions(+), 113 deletions(-) create mode 100644 packages/contracts/contracts/src/core/proxy/Factory.sol diff --git a/packages/contracts/contracts/src/core/interfaces/IFactory.sol b/packages/contracts/contracts/src/core/interfaces/IFactory.sol index dbe76e9..4e957eb 100644 --- a/packages/contracts/contracts/src/core/interfaces/IFactory.sol +++ b/packages/contracts/contracts/src/core/interfaces/IFactory.sol @@ -6,10 +6,9 @@ import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title IFactory /// @notice Base interface for Excubiae factories interface IFactory { - /// @notice Emitted when a new clone is deployed - /// @param instance Address of the deployed clone - event CloneDeployed(address indexed instance); + error InitializationFailed(); - /// @notice Returns the implementation contract address - function implementation() external view returns (address); + /// @notice Emitted when a new clone is deployed + /// @param clone Address of the deployed clone + event CloneDeployed(address indexed clone); } diff --git a/packages/contracts/contracts/src/core/proxy/Factory.sol b/packages/contracts/contracts/src/core/proxy/Factory.sol new file mode 100644 index 0000000..8add3c8 --- /dev/null +++ b/packages/contracts/contracts/src/core/proxy/Factory.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {LibClone} from "solady/src/utils/LibClone.sol"; +import {IFactory} from "../interfaces/IFactory.sol"; + +// @todo refactoring & comments +abstract contract Factory is IFactory { + address public immutable IMPLEMENTATION; + + constructor(address _implementation) { + IMPLEMENTATION = _implementation; + } + + function _deploy(bytes memory data) internal returns (address clone) { + clone = LibClone.clone(IMPLEMENTATION, data); + + emit CloneDeployed(clone); + } +} diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index fe299ef..38fd79b 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Test} from "forge-std/src/Test.sol"; +import {Test, Vm} from "forge-std/src/Test.sol"; import {NFT} from "./utils/NFT.sol"; import {BaseERC721Checker} from "./base/BaseERC721Checker.sol"; import {AdvancedERC721Checker} from "./advanced/AdvancedERC721Checker.sol"; @@ -23,6 +23,8 @@ import {Check} from "../core/interfaces/IAdvancedChecker.sol"; // @todo add edge cases contract AdvancedChecker is Test { + event CloneDeployed(address indexed clone); + NFT internal signupNft; NFT internal rewardNft; BaseERC721Checker internal baseChecker; @@ -39,31 +41,27 @@ contract AdvancedChecker is Test { function setUp() public virtual { vm.startPrank(deployer); - signupNft = new NFT(); rewardNft = new NFT(); - signupNft.mint(subject); - baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - address baseClone = baseFactory.createERC721Checker(address(signupNft)); + // For first deploy - capture the event + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - address advancedClone = advancedFactory.createERC721Checker( - address(signupNft), - address(rewardNft), - address(baseChecker), - 1, - 0, - 10 - ); - + // For second deploy - capture the event + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); advancedChecker = AdvancedERC721Checker(advancedClone); evidence[0] = abi.encode(0); - vm.stopPrank(); } @@ -331,22 +329,29 @@ contract AdvancedPolicy is Test { baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - address baseClone = baseFactory.createERC721Checker(address(signupNft)); + // For first deploy - capture the event + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - address advancedClone = advancedFactory.createERC721Checker( - address(signupNft), - address(rewardNft), - address(baseChecker), - 1, - 0, - 10 - ); + // For second deploy - capture the event + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); + advancedChecker = AdvancedERC721Checker(advancedClone); advancedChecker = AdvancedERC721Checker(advancedClone); policyFactory = new AdvancedERC721PolicyFactory(); - address policyClone = policyFactory.createERC721Policy(address(advancedChecker), false, false, false); + + // For first deploy - capture the event + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), false, false, false); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = AdvancedERC721Policy(policyClone); evidence[0] = abi.encode(0); @@ -1075,22 +1080,29 @@ contract Voting is Test { baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - address baseClone = baseFactory.createERC721Checker(address(signupNft)); + // For first deploy - capture the event + vm.recordLogs(); + baseFactory.deploy(address(signupNft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - address advancedClone = advancedFactory.createERC721Checker( - address(signupNft), - address(rewardNft), - address(baseChecker), - 1, - 0, - 10 - ); + // For second deploy - capture the event + vm.recordLogs(); + advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); + entries = vm.getRecordedLogs(); + address advancedClone = address(uint160(uint256(entries[0].topics[1]))); + advancedChecker = AdvancedERC721Checker(advancedClone); advancedChecker = AdvancedERC721Checker(advancedClone); policyFactory = new AdvancedERC721PolicyFactory(); - address policyClone = policyFactory.createERC721Policy(address(advancedChecker), false, false, false); + + // For first deploy - capture the event + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), false, false, false); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = AdvancedERC721Policy(policyClone); evidence[0] = abi.encode(0); diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index fbb1720..a3e250c 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {Test} from "forge-std/src/Test.sol"; +import {Test, Vm} from "forge-std/src/Test.sol"; import {NFT} from "./utils/NFT.sol"; import {BaseERC721Checker} from "./base/BaseERC721Checker.sol"; import {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; @@ -37,8 +37,13 @@ contract BaseChecker is Test { nft.mint(subject); factory = new BaseERC721CheckerFactory(); - address clone = factory.createERC721Checker(address(nft)); - checker = BaseERC721Checker(clone); + + // For first deploy - capture the event + vm.recordLogs(); + factory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address baseClone = address(uint160(uint256(entries[0].topics[1]))); + checker = BaseERC721Checker(baseClone); evidence[0] = abi.encode(0); @@ -152,11 +157,20 @@ contract BasePolicy is Test { nft.mint(subject); checkerFactory = new BaseERC721CheckerFactory(); - address checkerClone = checkerFactory.createERC721Checker(address(nft)); + policyFactory = new BaseERC721PolicyFactory(); + + // For first deploy - capture the event + vm.recordLogs(); + checkerFactory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address checkerClone = address(uint160(uint256(entries[0].topics[1]))); checker = BaseERC721Checker(checkerClone); - policyFactory = new BaseERC721PolicyFactory(); - address policyClone = policyFactory.createERC721Policy(address(checker)); + // For second deploy - capture the event + vm.recordLogs(); + policyFactory.deploy(address(checker)); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = BaseERC721Policy(policyClone); evidence[0] = abi.encode(0); @@ -410,13 +424,21 @@ contract Voting is Test { nft.mint(subject); checkerFactory = new BaseERC721CheckerFactory(); - address checkerClone = checkerFactory.createERC721Checker(address(nft)); + policyFactory = new BaseERC721PolicyFactory(); + + // For first deploy - capture the event + vm.recordLogs(); + checkerFactory.deploy(address(nft)); + Vm.Log[] memory entries = vm.getRecordedLogs(); + address checkerClone = address(uint160(uint256(entries[0].topics[1]))); checker = BaseERC721Checker(checkerClone); - policyFactory = new BaseERC721PolicyFactory(); - address policyClone = policyFactory.createERC721Policy(address(checker)); + // For second deploy - capture the event + vm.recordLogs(); + policyFactory.deploy(address(checker)); + entries = vm.getRecordedLogs(); + address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = BaseERC721Policy(policyClone); - voting = new BaseVoting(policy); vm.stopPrank(); diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol index 2a1fd23..f358224 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol @@ -3,24 +3,20 @@ pragma solidity ^0.8.20; import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; -contract AdvancedERC721CheckerFactory { - address public immutable erc721CheckerImplementation; +contract AdvancedERC721CheckerFactory is Factory { + constructor() Factory(address(new AdvancedERC721Checker())) {} - constructor() { - // Deploy the master ERC721Checker implementation once. - erc721CheckerImplementation = address(new AdvancedERC721Checker()); - } - - function createERC721Checker( + function deploy( address _nftAddress, address _rewardNft, address _baseERC721Checker, uint256 _minBalance, uint256 _minTokenId, uint256 _maxTokenId - ) external returns (address clone) { - // 1. Encode the address for appending. + ) public { + // 1. Encode. bytes memory data = abi.encode( _nftAddress, _rewardNft, @@ -30,10 +26,10 @@ contract AdvancedERC721CheckerFactory { _maxTokenId ); - // 2. Deploy the clone with appended data. - clone = LibClone.clone(erc721CheckerImplementation, data); + // 2. Deploy. + address clone = super._deploy(data); - // 3. Call initialize() so the new clone stores `_nftAddress` in `nft`. + // 3. Call initialize(). AdvancedERC721Checker(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol index 2616e20..ad63de5 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol @@ -3,39 +3,23 @@ pragma solidity ^0.8.20; import {LibClone} from "solady/src/utils/LibClone.sol"; import {AdvancedERC721Policy} from "./AdvancedERC721Policy.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; /** * @title AdvancedERC721PolicyFactory * @notice Example factory for deploying minimal proxies of `AdvancedERC721Policy`. */ -contract AdvancedERC721PolicyFactory { - /// @notice Address of the "master" (implementation) policy. - address public immutable advancedERC721PolicyImplementation; +contract AdvancedERC721PolicyFactory is Factory { + constructor() Factory(address(new AdvancedERC721Policy())) {} - constructor() { - // Deploy the logic contract once. - // Or set it externally if already deployed. - advancedERC721PolicyImplementation = address(new AdvancedERC721Policy()); - } - - /** - * @notice Deploys a new minimal proxy clone, passing in `_checkerAddr` for initialization. - * @param _checkerAddr The address of the BaseERC721Checker to use. - * @return clone The address of the newly deployed clone. - */ - function createERC721Policy( - address _checkerAddr, - bool _skipPre, - bool _skipPost, - bool _allowMultipleMain - ) external returns (address clone) { - // 1. Encode the checker address for appending. + function deploy(address _checkerAddr, bool _skipPre, bool _skipPost, bool _allowMultipleMain) public { + // 1. Encode. bytes memory data = abi.encode(msg.sender, _checkerAddr, _skipPre, _skipPost, _allowMultipleMain); - // 2. Deploy the clone with appended data. - clone = LibClone.clone(advancedERC721PolicyImplementation, data); + // 2. Deploy. + address clone = super._deploy(data); - // 3. Call `initialize()` so the clone sets up its owner (the factory) + checker reference. + // 3. Call `initialize()`. AdvancedERC721Policy(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol index c261b94..3c4a70f 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol @@ -2,22 +2,18 @@ pragma solidity ^0.8.20; import {BaseERC721Checker} from "./BaseERC721Checker.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; -contract BaseERC721CheckerFactory { - address public immutable erc721CheckerImplementation; +contract BaseERC721CheckerFactory is Factory { + constructor() Factory(address(new BaseERC721Checker())) {} - constructor() { - // Deploy the master ERC721Checker implementation once. - erc721CheckerImplementation = address(new BaseERC721Checker()); - } - - function createERC721Checker(address _nftAddress) external returns (address clone) { + function deploy(address _nftAddress) public { // 1. Encode the address for appending. bytes memory data = abi.encode(_nftAddress); // 2. Deploy the clone with appended data. - clone = LibClone.clone(erc721CheckerImplementation, data); + address clone = super._deploy(data); // 3. Call initialize() so the new clone stores `_nftAddress` in `nft`. BaseERC721Checker(clone).initialize(); diff --git a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol index 7ebe282..5441730 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol @@ -3,32 +3,21 @@ pragma solidity ^0.8.20; import {LibClone} from "solady/src/utils/LibClone.sol"; import {BaseERC721Policy} from "./BaseERC721Policy.sol"; +import {Factory} from "../../core/proxy/Factory.sol"; /** * @title BaseERC721PolicyFactory * @notice Example factory for deploying minimal proxies of `BaseERC721Policy`. */ -contract BaseERC721PolicyFactory { - /// @notice Address of the "master" (implementation) policy. - address public immutable baseERC721PolicyImplementation; +contract BaseERC721PolicyFactory is Factory { + constructor() Factory(address(new BaseERC721Policy())) {} - constructor() { - // Deploy the logic contract once. - // Or set it externally if already deployed. - baseERC721PolicyImplementation = address(new BaseERC721Policy()); - } - - /** - * @notice Deploys a new minimal proxy clone, passing in `_checkerAddr` for initialization. - * @param _checkerAddr The address of the BaseERC721Checker to use. - * @return clone The address of the newly deployed clone. - */ - function createERC721Policy(address _checkerAddr) external returns (address clone) { - // 1. Encode the checker address for appending. + function deploy(address _checkerAddr) public { + // 1. Encode. bytes memory data = abi.encode(msg.sender, _checkerAddr); // 2. Deploy the clone with appended data. - clone = LibClone.clone(baseERC721PolicyImplementation, data); + address clone = super._deploy(data); // 3. Call `initialize()` so the clone sets up its owner (the factory) + checker reference. BaseERC721Policy(clone).initialize(); From 71f7911179d5cea626ee89e4cb133bb7dd363493 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 28 Jan 2025 18:27:03 +0100 Subject: [PATCH 07/12] docs(contracts): add code documentation to core contracts --- .../src/core/checker/AdvancedChecker.sol | 60 ++++++++++--------- .../src/core/checker/BaseChecker.sol | 23 +++---- .../src/core/interfaces/IAdvancedChecker.sol | 38 ++++++------ .../src/core/interfaces/IAdvancedPolicy.sol | 33 +++++----- .../src/core/interfaces/IBaseChecker.sol | 17 +++--- .../src/core/interfaces/IBasePolicy.sol | 18 +++--- .../src/core/interfaces/IChecker.sol | 9 --- .../contracts/src/core/interfaces/IClone.sol | 8 +++ .../src/core/interfaces/IFactory.sol | 9 ++- .../contracts/src/core/interfaces/IPolicy.sol | 31 +++++++--- .../src/core/policy/AdvancedPolicy.sol | 35 ++++++----- .../contracts/src/core/policy/BasePolicy.sol | 33 ++++------ .../contracts/src/core/policy/Policy.sol | 42 ++++++------- .../contracts/src/core/proxy/Clone.sol | 22 ++++++- .../contracts/src/core/proxy/Factory.sol | 13 +++- .../contracts/src/test/Advanced.t.sol | 1 - .../contracts/contracts/src/test/Base.t.sol | 1 - 17 files changed, 211 insertions(+), 182 deletions(-) delete mode 100644 packages/contracts/contracts/src/core/interfaces/IChecker.sol diff --git a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol index 3843d2b..b89c752 100644 --- a/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/checker/AdvancedChecker.sol @@ -4,15 +4,17 @@ pragma solidity ^0.8.20; import {IAdvancedChecker, Check, CheckStatus} from "../interfaces/IAdvancedChecker.sol"; import {Clone} from "../proxy/Clone.sol"; -/// @title AdvancedChecker. -/// @notice Multi-phase validation checker with pre, main, and post checks. -/// @dev Base contract for implementing complex validation logic with configurable check phases. +/// @title AdvancedChecker +/// @notice Abstract contract for multi-phase validation (PRE, MAIN, POST). +/// @dev Implements advanced validation by routing checks to appropriate phases. +/// This is intended to be extended for complex validation systems. abstract contract AdvancedChecker is Clone, IAdvancedChecker { - /// @notice Entry point for validation checks. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check (PRE, MAIN, POST). - /// @return checked Validation result. + /// @notice Validates a subject's evidence for a specific check phase. + /// @dev External entry point for validation checks, delegating logic to `_check`. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute (PRE, MAIN, POST). + /// @return checked Boolean indicating whether the validation passed. function check( address subject, bytes[] calldata evidence, @@ -21,12 +23,12 @@ abstract contract AdvancedChecker is Clone, IAdvancedChecker { return _check(subject, evidence, checkType); } - /// @notice Core validation logic router. - /// @dev Directs to appropriate check based on type and configuration. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check type to perform. - /// @return checked Validation result. + /// @notice Core validation logic dispatcher. + /// @dev Routes validation calls to specific phase methods (_checkPre, _checkMain, _checkPost). + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute. + /// @return checked Boolean indicating whether the validation passed. function _check(address subject, bytes[] calldata evidence, Check checkType) internal view returns (bool checked) { if (checkType == Check.PRE) { return _checkPre(subject, evidence); @@ -39,24 +41,24 @@ abstract contract AdvancedChecker is Clone, IAdvancedChecker { return _checkMain(subject, evidence); } - /// @notice Pre-condition validation implementation. - /// @dev Override to implement pre-check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. + /// @notice Pre-condition validation logic. + /// @dev Derived contracts should override this to implement pre-check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function _checkPre(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} - /// @notice Main validation implementation. - /// @dev Override to implement main check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. + /// @notice Main validation logic. + /// @dev Derived contracts should override this to implement main check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function _checkMain(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} - /// @notice Post-condition validation implementation. - /// @dev Override to implement post-check logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked Validation result. + /// @notice Post-condition validation logic. + /// @dev Derived contracts should override this to implement post-check validation. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function _checkPost(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} } diff --git a/packages/contracts/contracts/src/core/checker/BaseChecker.sol b/packages/contracts/contracts/src/core/checker/BaseChecker.sol index 26b4f75..0b3bdf2 100644 --- a/packages/contracts/contracts/src/core/checker/BaseChecker.sol +++ b/packages/contracts/contracts/src/core/checker/BaseChecker.sol @@ -6,22 +6,23 @@ import {Clone} from "../proxy/Clone.sol"; /// @title BaseChecker /// @notice Abstract base contract for implementing validation checks. -/// @dev Provides a standardized interface for implementing custom validation logic -/// through the internal _check method. +/// @dev This contract provides a standardized interface for validation logic, delegating +/// actual implementation to the internal `_check` method. It is clone-compatible. abstract contract BaseChecker is Clone, IBaseChecker { - /// @notice Validates evidence for a given subject address. - /// @dev External view function that delegates to internal _check implementation. - /// @param subject Address to validate. - /// @param evidence Custom validation data. - /// @return checked Boolean indicating if the check passed. + /// @notice Validates a subject's evidence. + /// @dev External view function that calls the `_check` method, allowing derived contracts + /// to implement custom validation logic. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function check(address subject, bytes[] calldata evidence) external view override returns (bool checked) { return _check(subject, evidence); } /// @notice Internal validation logic implementation. - /// @dev Must be implemented by derived contracts. - /// @param subject Address to validate. - /// @param evidence Custom validation data. - /// @return checked Boolean indicating if the check passed. + /// @dev Must be overridden by derived contracts to define custom validation rules. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function _check(address subject, bytes[] calldata evidence) internal view virtual returns (bool checked) {} } diff --git a/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol index c75fbdf..39bc38b 100644 --- a/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol +++ b/packages/contracts/contracts/src/core/interfaces/IAdvancedChecker.sol @@ -1,36 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -/// @title Check. -/// @notice Defines validation phases in the AdvancedChecker system. -/// @custom:values PRE - Pre-condition validation. -/// MAIN - Primary validation. -/// POST - Post-condition validation. +/// @title Check +/// @notice Enum representing validation phases. +/// @dev Used to identify the specific validation phase in multi-phase systems. enum Check { + /// Pre-condition validation. PRE, + /// Primary validation. MAIN, + /// Post-condition validation. POST } -/// @notice Tracks validation status for pre, main, and post checks. -/// @dev Used to maintain check state in AdvancedPolicy. +/// @notice Tracks the status of validation checks. +/// @dev Used in AdvancedPolicy to maintain the state of multi-phase checks. struct CheckStatus { - /// @dev Pre-check completion status. + /// @notice Indicates whether the pre-condition check has been completed. bool pre; - /// @dev Number of completed main checks. + /// @notice Tracks the number of main checks completed. uint8 main; - /// @dev Post-check completion status. + /// @notice Indicates whether the post-condition check has been completed. bool post; } -/// @title IAdvancedChecker. -/// @notice Defines multi-phase validation system interface. -/// @dev Implement this for custom validation logic with pre/main/post checks. +/// @title IAdvancedChecker +/// @notice Interface defining multi-phase validation capabilities. +/// @dev Supports PRE, MAIN, and POST validation phases. interface IAdvancedChecker { - /// @notice Validates subject against specified check type. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check phase to execute. - /// @return checked True if validation passes. + /// @notice Validates a subject for a specific check phase. + /// @dev Implementations should route to appropriate phase-specific logic. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @param checkType The phase of validation to execute (PRE, MAIN, POST). + /// @return checked Boolean indicating whether the validation passed. function check(address subject, bytes[] calldata evidence, Check checkType) external view returns (bool checked); } diff --git a/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol index d9f719d..62a3626 100644 --- a/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol +++ b/packages/contracts/contracts/src/core/interfaces/IAdvancedPolicy.sol @@ -4,35 +4,36 @@ pragma solidity ^0.8.20; import {IPolicy} from "./IPolicy.sol"; import {Check} from "./IAdvancedChecker.sol"; -/// @title IAdvancedPolicy. -/// @notice Extends IPolicy with multi-phase validation capabilities. +/// @title IAdvancedPolicy +/// @notice Extends IPolicy with support for multi-phase validation checks (pre, main, post). +/// @dev Adds granular error reporting and event logging for advanced enforcement scenarios. interface IAdvancedPolicy is IPolicy { - /// @notice Thrown when multiple main checks not allowed. + /// @notice Error thrown when multiple main checks are attempted but not allowed. error MainCheckAlreadyEnforced(); - /// @notice Thrown when main check attempted before pre-check. + /// @notice Error thrown when a main check is attempted without a prior pre-check. error PreCheckNotEnforced(); - /// @notice Thrown when post check attempted before main check. + /// @notice Error thrown when a post-check is attempted without a prior main check. error MainCheckNotEnforced(); - /// @notice Thrown when pre-check validation attempted while skipped. + /// @notice Error thrown when a pre-check is attempted while pre-checks are skipped. error CannotPreCheckWhenSkipped(); - /// @notice Thrown when post-check validation attempted while skipped. + /// @notice Error thrown when a post-check is attempted while post-checks are skipped. error CannotPostCheckWhenSkipped(); - /// @notice Emitted when validation check succeeds. - /// @param subject Address that passed validation. - /// @param target Protected contract address. - /// @param evidence Validation data. - /// @param checkType Type of check performed. + /// @notice Emitted when a subject successfully passes a validation check. + /// @param subject Address that passed the validation. + /// @param target Address of the protected contract. + /// @param evidence Data used during validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). event Enforced(address indexed subject, address indexed target, bytes[] evidence, Check checkType); - /// @notice Enforces validation check on subject. - /// @dev Delegates to appropriate check method based on checkType. + /// @notice Enforces a specific phase of the policy check on a given subject. + /// @dev Delegates validation logic to the corresponding phase's check method. /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check phase to execute. + /// @param evidence Data required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). function enforce(address subject, bytes[] calldata evidence, Check checkType) external; } diff --git a/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol b/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol index d2f923d..299b520 100644 --- a/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol +++ b/packages/contracts/contracts/src/core/interfaces/IBaseChecker.sol @@ -1,14 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {IChecker} from "./IChecker.sol"; - -/// @title IBaseChecker. -/// @notice Defines base validation functionality. -interface IBaseChecker is IChecker { - /// @notice Validates subject against evidence. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return checked True if validation passes. +/// @title IBaseChecker +/// @notice Interface defining base validation functionality for policies. +/// @dev Contracts implementing this interface must define the `check` method. +interface IBaseChecker { + /// @notice Validates a subject against provided evidence. + /// @param subject The address to validate. + /// @param evidence An array of custom validation data. + /// @return checked Boolean indicating whether the validation passed. function check(address subject, bytes[] calldata evidence) external view returns (bool checked); } diff --git a/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol b/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol index faef64e..9fb1a6a 100644 --- a/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol +++ b/packages/contracts/contracts/src/core/interfaces/IBasePolicy.sol @@ -3,17 +3,19 @@ pragma solidity ^0.8.20; import {IPolicy} from "./IPolicy.sol"; -/// @title IBasePolicy. -/// @notice Extends IPolicy with basic validation capabilities. +/// @title IBasePolicy +/// @notice Extends IPolicy with basic validation and enforcement capabilities. +/// @dev Adds event logging and a method to enforce policy checks. interface IBasePolicy is IPolicy { - /// @notice Emitted when validation succeeds. - /// @param subject Address that passed validation. - /// @param target Protected contract address. - /// @param evidence Validation data. + /// @notice Emitted when a subject successfully passes a policy enforcement check. + /// @param subject Address that passed the validation. + /// @param target Address of the protected contract. + /// @param evidence Data used during validation. event Enforced(address indexed subject, address indexed target, bytes[] evidence); - /// @notice Enforces validation check on subject. + /// @notice Enforces a validation check on a given subject. + /// @dev This method ensures that the provided subject meets the policy's criteria. /// @param subject Address to validate. - /// @param evidence Validation data. + /// @param evidence Data required for validation. function enforce(address subject, bytes[] calldata evidence) external; } diff --git a/packages/contracts/contracts/src/core/interfaces/IChecker.sol b/packages/contracts/contracts/src/core/interfaces/IChecker.sol deleted file mode 100644 index 3b1b847..0000000 --- a/packages/contracts/contracts/src/core/interfaces/IChecker.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -/// @title IChecker -/// @notice Core checker interface for attribute verification functionalities. -interface IChecker { - /// @notice Core error conditions. - error VerifierNotFound(); -} diff --git a/packages/contracts/contracts/src/core/interfaces/IClone.sol b/packages/contracts/contracts/src/core/interfaces/IClone.sol index 8f9648d..ae70942 100644 --- a/packages/contracts/contracts/src/core/interfaces/IClone.sol +++ b/packages/contracts/contracts/src/core/interfaces/IClone.sol @@ -1,10 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +/// @title IClone +/// @notice Interface for cloneable contracts with initialization logic. +/// @dev Supports minimal proxy pattern and appended bytes retrieval. interface IClone { + /// @notice Error thrown when the clone is already initialized. error AlreadyInitialized(); + /// @notice Initializes the clone contract. + /// @dev Typically used for setting up state or configuration data. function initialize() external; + /// @notice Retrieves appended bytes from the clone's runtime bytecode. + /// @return Appended bytes passed during the clone's creation. function getAppendedBytes() external returns (bytes memory); } diff --git a/packages/contracts/contracts/src/core/interfaces/IFactory.sol b/packages/contracts/contracts/src/core/interfaces/IFactory.sol index 4e957eb..29e752a 100644 --- a/packages/contracts/contracts/src/core/interfaces/IFactory.sol +++ b/packages/contracts/contracts/src/core/interfaces/IFactory.sol @@ -4,11 +4,10 @@ pragma solidity ^0.8.20; import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title IFactory -/// @notice Base interface for Excubiae factories +/// @notice Base interface for factory contracts responsible for deploying minimal proxy clones. +/// @dev Provides methods for clone deployment and related events. interface IFactory { - error InitializationFailed(); - - /// @notice Emitted when a new clone is deployed - /// @param clone Address of the deployed clone + /// @notice Emitted when a new clone contract is successfully deployed. + /// @param clone Address of the deployed clone contract. event CloneDeployed(address indexed clone); } diff --git a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol index 9541a64..4c1afe2 100644 --- a/packages/contracts/contracts/src/core/interfaces/IPolicy.sol +++ b/packages/contracts/contracts/src/core/interfaces/IPolicy.sol @@ -1,26 +1,39 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -/// @title IPolicy. -/// @notice Core policy interface for protected contract management. +/// @title IPolicy +/// @notice Core interface for managing policies that protect specific contracts. +/// @dev Provides methods for setting and retrieving the protected contract and enforcing checks. interface IPolicy { - /// @notice Emitted when target contract is set. + /// @notice Emitted when the target contract is successfully set. + /// @param target Address of the protected contract. event TargetSet(address indexed target); - /// @notice Core error conditions. + /// @notice Error thrown when a zero address is provided where not allowed. error ZeroAddress(); + + /// @notice Error thrown when a validation check fails. error UnsuccessfulCheck(); + + /// @notice Error thrown when the target contract is not set. error TargetNotSet(); + + /// @notice Error thrown when a function is restricted to calls from the target contract. error TargetOnly(); + + /// @notice Error thrown when attempting to set the target more than once. error TargetAlreadySet(); + + /// @notice Error thrown when a subject is already enforced. error AlreadyEnforced(); - /// @notice Returns policy trait identifier. - /// @return Policy trait string (e.g., "Semaphore"). + /// @notice Retrieves the policy trait identifier. + /// @dev This is typically used to distinguish policy implementations (e.g., "Semaphore"). + /// @return The policy trait string. function trait() external pure returns (string memory); - /// @notice Sets protected contract address. - /// @dev Owner-only, one-time setting. - /// @param _target Protected contract address. + /// @notice Sets the contract address to be protected by this policy. + /// @dev This function is restricted to the owner and can only be called once. + /// @param _target The address of the protected contract. function setTarget(address _target) external; } diff --git a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol index 8cf5e18..69f06fc 100644 --- a/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol +++ b/packages/contracts/contracts/src/core/policy/AdvancedPolicy.sol @@ -7,10 +7,10 @@ import {Policy} from "./Policy.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title AdvancedPolicy -/// @notice Implements advanced policy checks with pre, main, and post validation stages. -/// @dev Extends Policy with multi-stage validation. Now clone-friendly with `initialize()`. +/// @notice Implements multi-stage policy checks with pre, main, and post validation stages. +/// @dev Extends Policy and provides advanced enforcement logic with an AdvancedChecker. abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { - /// @notice Reference to the validation checker contract. Stored, not immutable. + /// @notice Reference to the AdvancedChecker contract used for validation. AdvancedChecker public ADVANCED_CHECKER; /// @notice Controls whether pre-condition checks are required. @@ -22,18 +22,14 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { /// @notice Controls whether main check can be executed multiple times. bool public ALLOW_MULTIPLE_MAIN; - /// @notice Tracks validation status for each subject per target. + /// @notice Tracks enforcement status for each subject for each phase. mapping(address => CheckStatus) public enforced; - /** - * @notice Initialize function for minimal proxy clones. - * Decodes appended bytes for (AdvancedChecker, skipPre, skipPost, allowMultipleMain). - */ + /// @notice Initializes the contract with appended bytes data for configuration. + /// @dev Decodes AdvancedChecker address and sets the owner. function _initialize() internal virtual override { - // 1. Call Policy’s initialize to set ownership and `_initialized`. super._initialize(); - // 2. Decode the appended bytes for the advanced config. bytes memory data = _getAppendedBytes(); (address sender, address advCheckerAddr, bool skipPre, bool skipPost, bool allowMultipleMain) = abi.decode( data, @@ -48,18 +44,20 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { ALLOW_MULTIPLE_MAIN = allowMultipleMain; } - /// @notice Enforces a policy check for a subject, handling multi-stage logic. - /// @dev Only callable by the target contract. + /// @notice Enforces a multi-stage policy check. + /// @dev Handles pre, main, and post validation stages. Only callable by the target contract. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). function enforce(address subject, bytes[] calldata evidence, Check checkType) external override onlyTarget { _enforce(subject, evidence, checkType); } - /// @notice Internal check enforcement logic for advanced multi-stage checks. + /// @notice Internal implementation of multi-stage enforcement logic. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. + /// @param checkType The type of check performed (PRE, MAIN, POST). function _enforce(address subject, bytes[] calldata evidence, Check checkType) internal { - if (!ADVANCED_CHECKER.check(subject, evidence, checkType)) { - revert UnsuccessfulCheck(); - } - CheckStatus storage status = enforced[subject]; if (checkType == Check.PRE) { @@ -72,12 +70,13 @@ abstract contract AdvancedPolicy is IAdvancedPolicy, Policy { if (status.post) revert AlreadyEnforced(); status.post = true; } else { - // MAIN check if (!SKIP_PRE && !status.pre) revert PreCheckNotEnforced(); if (!ALLOW_MULTIPLE_MAIN && status.main > 0) revert MainCheckAlreadyEnforced(); status.main += 1; } + if (!ADVANCED_CHECKER.check(subject, evidence, checkType)) revert UnsuccessfulCheck(); + emit Enforced(subject, target, evidence, checkType); } } diff --git a/packages/contracts/contracts/src/core/policy/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol index 51f8107..ca848ef 100644 --- a/packages/contracts/contracts/src/core/policy/BasePolicy.sol +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -7,49 +7,42 @@ import {BaseChecker} from "../checker/BaseChecker.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title BasePolicy -/// @notice Abstract base contract for implementing specific policy checks. -/// @dev Inherits from Policy and implements IBasePolicy interface. -/// Now uses an `initialize()` function for minimal proxy clones. +/// @notice Abstract base contract for implementing custom policies using a BaseChecker. +/// @dev Extends Policy and provides enforcement logic using a BaseChecker instance. abstract contract BasePolicy is Policy, IBasePolicy { /// @notice Reference to the BaseChecker contract used for validation. - /// @dev Stored in normal storage (not immutable) so it can be set in `initialize()`. BaseChecker public BASE_CHECKER; - /// @notice Tracks enforcement status for each subject per target. + /// @notice Tracks enforcement status for each subject. mapping(address => bool) public enforced; - /** - * @notice Initializes the contract with a BaseChecker instance, reading from appended bytes. - * Replaces the old constructor-based approach. - */ + /// @notice Initializes the contract with appended bytes data for configuration. + /// @dev Decodes BaseChecker address and sets the owner. function _initialize() internal virtual override { - // 1. Call the base `Policy.initialize()` to set ownership / handle `_initialized`. super._initialize(); - // 2. Decode the appended bytes to get the BaseChecker address (and anything else you might need). bytes memory data = _getAppendedBytes(); (address sender, address baseCheckerAddr) = abi.decode(data, (address, address)); _transferOwnership(sender); - // 3. Store in the contract’s storage (previously `immutable`). BASE_CHECKER = BaseChecker(baseCheckerAddr); } - /// @notice External function to enforce policy checks. - /// @dev Only callable by the target contract. - /// @param subject Address to enforce the check on. - /// @param evidence Additional data required for verification. + /// @notice Enforces a policy check for a subject. + /// @dev Uses the BaseChecker for validation logic. Only callable by the target contract. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. function enforce(address subject, bytes[] calldata evidence) external override onlyTarget { _enforce(subject, evidence); } - /// @notice Internal implementation of enforcement logic. + /// @notice Internal logic for enforcing policy checks. + /// @param subject Address to enforce the policy on. + /// @param evidence Evidence required for validation. function _enforce(address subject, bytes[] memory evidence) internal { - bool checked = BASE_CHECKER.check(subject, evidence); - if (enforced[subject]) revert AlreadyEnforced(); - if (!checked) revert UnsuccessfulCheck(); + if (!BASE_CHECKER.check(subject, evidence)) revert UnsuccessfulCheck(); enforced[subject] = true; diff --git a/packages/contracts/contracts/src/core/policy/Policy.sol b/packages/contracts/contracts/src/core/policy/Policy.sol index 1bbc518..9a7ebbe 100644 --- a/packages/contracts/contracts/src/core/policy/Policy.sol +++ b/packages/contracts/contracts/src/core/policy/Policy.sol @@ -4,24 +4,33 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IPolicy} from "../interfaces/IPolicy.sol"; import {Clone} from "../proxy/Clone.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; +/// @title Policy +/// @notice Abstract base contract for implementing policies to enforce access control. +/// @dev Extends Clone and Ownable to provide policy initialization, ownership, and target management. abstract contract Policy is Clone, IPolicy, Ownable(msg.sender) { - /// @notice The “gatekeeped” contract address set once by the owner (if at all). - address internal target; + /// @notice The address of the contract being protected by the policy. + /// @dev Can only be set once by the owner. + address public target; + /// @notice Modifier to restrict access to only the target contract. + modifier onlyTarget() { + if (msg.sender != target) revert TargetOnly(); + _; + } + + /// @notice Initializes the contract and sets the owner. + /// @dev Overrides Clone's `_initialize` to include owner setup. function _initialize() internal virtual override { super._initialize(); - // By default, set the owner to the caller (likely the factory). - // this is not the zero address as above! + // Sets the factory as the initial owner. _transferOwnership(msg.sender); } - /** - * @notice Only the owner can call `setTarget` once. - * @param _target The contract to be protected by this policy. - */ + /// @notice Sets the contract address to be protected by this policy. + /// @dev Can only be called once by the owner. + /// @param _target The contract address to protect. function setTarget(address _target) external virtual onlyOwner { if (_target == address(0)) revert ZeroAddress(); if (target != address(0)) revert TargetAlreadySet(); @@ -29,19 +38,4 @@ abstract contract Policy is Clone, IPolicy, Ownable(msg.sender) { target = _target; emit TargetSet(_target); } - - /** - * @notice A helper getter for the `target`. - */ - function getTarget() external view returns (address) { - return target; - } - - /** - * @notice A modifier that restricts a function to only be called by `target`. - */ - modifier onlyTarget() { - if (msg.sender != target) revert TargetOnly(); - _; - } } diff --git a/packages/contracts/contracts/src/core/proxy/Clone.sol b/packages/contracts/contracts/src/core/proxy/Clone.sol index a77124f..63a9321 100644 --- a/packages/contracts/contracts/src/core/proxy/Clone.sol +++ b/packages/contracts/contracts/src/core/proxy/Clone.sol @@ -4,24 +4,40 @@ pragma solidity ^0.8.20; import {IClone} from "../interfaces/IClone.sol"; import {LibClone} from "solady/src/utils/LibClone.sol"; -// @todo refactoring & comments +/// @title Clone +/// @notice Abstract base contract for creating cloneable contracts with initialization logic. +/// @dev Provides utilities for managing clone initialization and retrieving appended arguments. abstract contract Clone is IClone { + /// @notice Tracks whether the clone has been initialized. + /// @dev Prevents re-initialization through the `_initialize` function. bool private _initialized; + /// @notice Initializes the clone. + /// @dev Calls the internal `_initialize` function to set up the clone. + /// Reverts if the clone is already initialized. function initialize() external { _initialize(); } - function getAppendedBytes() external returns (bytes memory) { + /// @notice Retrieves appended arguments from the clone. + /// @dev Leverages `LibClone` to extract arguments from the clone's runtime bytecode. + /// @return appendedBytes The appended bytes extracted from the clone. + function getAppendedBytes() external returns (bytes memory appendedBytes) { return _getAppendedBytes(); } + /// @notice Internal function to initialize the clone. + /// @dev Must be overridden by derived contracts to implement custom initialization logic. + /// Reverts if the clone has already been initialized. function _initialize() internal virtual { if (_initialized) revert AlreadyInitialized(); _initialized = true; } - function _getAppendedBytes() internal virtual returns (bytes memory) { + /// @notice Internal function to retrieve appended arguments from the clone. + /// @dev Uses `LibClone` utility to extract the arguments. + /// @return appendedBytes The appended bytes extracted from the clone. + function _getAppendedBytes() internal virtual returns (bytes memory appendedBytes) { return LibClone.argsOnClone(address(this)); } } diff --git a/packages/contracts/contracts/src/core/proxy/Factory.sol b/packages/contracts/contracts/src/core/proxy/Factory.sol index 8add3c8..dacbcd4 100644 --- a/packages/contracts/contracts/src/core/proxy/Factory.sol +++ b/packages/contracts/contracts/src/core/proxy/Factory.sol @@ -4,14 +4,25 @@ pragma solidity ^0.8.20; import {LibClone} from "solady/src/utils/LibClone.sol"; import {IFactory} from "../interfaces/IFactory.sol"; -// @todo refactoring & comments +/// @title Factory +/// @notice Abstract base contract for deploying clone contracts. +/// @dev Provides functionality to deploy minimal proxy contracts using a standard implementation address. abstract contract Factory is IFactory { + /// @notice Address of the implementation contract used for cloning. + /// @dev This address is immutable and defines the logic contract for all clones deployed by the factory. address public immutable IMPLEMENTATION; + /// @notice Initializes the factory with the implementation contract address. + /// @param _implementation Address of the logic contract to use for clones. constructor(address _implementation) { IMPLEMENTATION = _implementation; } + /// @notice Deploys a new clone contract. + /// @dev Uses `LibClone` to deploy a minimal proxy contract with appended initialization data. + /// Emits a `CloneDeployed` event upon successful deployment. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. function _deploy(bytes memory data) internal returns (address clone) { clone = LibClone.clone(IMPLEMENTATION, data); diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index 38fd79b..e784aed 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -13,7 +13,6 @@ import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; import {BaseVoting} from "./base/BaseVoting.sol"; import {AdvancedVoting} from "./advanced/AdvancedVoting.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; -import {IChecker} from "../core/interfaces/IChecker.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Check} from "../core/interfaces/IAdvancedChecker.sol"; diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index a3e250c..473c453 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -9,7 +9,6 @@ import {BaseERC721PolicyFactory} from "./base/BaseERC721PolicyFactory.sol"; import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; import {BaseVoting} from "./base/BaseVoting.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; -import {IChecker} from "../core/interfaces/IChecker.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; From a2695f4ada97b9788885e502b6e720b4bf479834 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 28 Jan 2025 19:30:00 +0100 Subject: [PATCH 08/12] refactor(contracts): add missing harnesses and comments to base and advanced test contracts --- .../test/advanced/AdvancedERC721Checker.sol | 48 ++++---- .../advanced/AdvancedERC721CheckerFactory.sol | 16 ++- .../test/advanced/AdvancedERC721Policy.sol | 10 +- .../advanced/AdvancedERC721PolicyFactory.sol | 17 +-- .../src/test/advanced/AdvancedVoting.sol | 105 ++++++++++-------- .../src/test/base/BaseERC721Checker.sol | 23 ++-- .../test/base/BaseERC721CheckerFactory.sol | 13 ++- .../src/test/base/BaseERC721Policy.sol | 14 +-- .../src/test/base/BaseERC721PolicyFactory.sol | 16 +-- .../contracts/src/test/base/BaseVoting.sol | 59 +++++----- .../AdvancedERC721CheckerFactoryHarness.sol | 16 +++ .../wrappers/AdvancedERC721CheckerHarness.sol | 80 ++++++------- .../AdvancedERC721PolicyFactoryHarness.sol | 16 +++ .../wrappers/AdvancedERC721PolicyHarness.sol | 32 +++--- .../BaseERC721CheckerFactoryHarness.sol | 16 +++ .../wrappers/BaseERC721CheckerHarness.sol | 32 +++--- .../BaseERC721PolicyFactoryHarness.sol | 16 +++ .../test/wrappers/BaseERC721PolicyHarness.sol | 25 +++-- 18 files changed, 314 insertions(+), 240 deletions(-) create mode 100644 packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol create mode 100644 packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol create mode 100644 packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol create mode 100644 packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol index 39e71c7..18461ec 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Checker.sol @@ -5,18 +5,14 @@ import {AdvancedChecker} from "../../core/checker/AdvancedChecker.sol"; import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -/// @title AdvancedERC721Checker. -/// @notice Multi-phase NFT validation with aggregated verification contracts. -/// @dev Implements three-phase validation using multiple NFT contracts and external verifiers: -/// - Pre-check: Basic signup token validation using BaseERC721Checker. -/// - Main-check: Balance threshold validation for signup token. -/// - Post-check: Reward eligibility verification for reward token. +/// @title AdvancedERC721Checker +/// @notice Multi-phase NFT validation using external contracts and thresholds. +/// @dev Implements three-phase validation: +/// - Pre-check: Basic ownership verification using `BaseERC721Checker`. +/// - Main-check: Ensures a minimum token balance. +/// - Post-check: Validates reward eligibility. contract AdvancedERC721Checker is AdvancedChecker { - /// @notice External contracts used for verification. - /// @dev Immutable references derived from verifier array positions: - /// - Index 0: Signup NFT contract. - /// - Index 1: Reward NFT contract. - /// - Index 2: Base ERC721 checker contract. + /// @notice External verification contracts and thresholds. IERC721 public signupNft; IERC721 public rewardNft; BaseERC721Checker public baseERC721Checker; @@ -25,14 +21,13 @@ contract AdvancedERC721Checker is AdvancedChecker { uint256 public minTokenId; uint256 public maxTokenId; + /// @notice Initializes the checker with external contract references and thresholds. + /// @dev Decodes appended bytes to set state variables. function _initialize() internal override { - // 1. Call super to handle `_initialized` check. super._initialize(); - // 2. Retrieve appended bytes from the clone. bytes memory data = _getAppendedBytes(); - // 3. Decode everything in one shot: ( address signupNftAddr, address rewardNftAddr, @@ -42,7 +37,6 @@ contract AdvancedERC721Checker is AdvancedChecker { uint256 maxTokenId_ ) = abi.decode(data, (address, address, address, uint256, uint256, uint256)); - // 4. Assign to storage variables. signupNft = IERC721(signupNftAddr); rewardNft = IERC721(rewardNftAddr); baseERC721Checker = BaseERC721Checker(baseCheckerAddr); @@ -51,33 +45,33 @@ contract AdvancedERC721Checker is AdvancedChecker { maxTokenId = maxTokenId_; } - /// @notice Pre-check: Validates initial NFT ownership. - /// @dev Delegates basic ownership check to BaseERC721Checker. + /// @notice Pre-check: Validates ownership using the base checker. /// @param subject Address to validate. - /// @param evidence Array containing encoded tokenId. - /// @return Validation status from base checker. + /// @param evidence Encoded tokenId. + /// @return Boolean indicating validation success. function _checkPre(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPre(subject, evidence); + return baseERC721Checker.check(subject, evidence); } - /// @notice Main-check: Validates token balance requirements. - /// @dev Ensures subject has exactly MIN_BALANCE tokens. + /// @notice Main-check: Ensures token balance meets requirements. /// @param subject Address to validate. - /// @param evidence Not used in balance check. - /// @return True if balance meets requirements. + /// @param evidence Not used in this validation. + /// @return Boolean indicating validation success. function _checkMain(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkMain(subject, evidence); - return signupNft.balanceOf(subject) >= minBalance && signupNft.balanceOf(subject) <= minBalance; + + return signupNft.balanceOf(subject) >= minBalance; } /// @notice Post-check: Validates reward eligibility. - /// @dev Ensures subject doesn't already have reward tokens. /// @param subject Address to validate. - /// @param evidence Not used in reward check. - /// @return True if subject eligible for rewards. + /// @param evidence Not used in this validation. + /// @return Boolean indicating validation success. function _checkPost(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._checkPost(subject, evidence); + return rewardNft.balanceOf(subject) == 0; } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol index f358224..89c68e8 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721CheckerFactory.sol @@ -2,12 +2,23 @@ pragma solidity ^0.8.20; import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; import {Factory} from "../../core/proxy/Factory.sol"; +/// @title AdvancedERC721CheckerFactory +/// @notice Factory for deploying minimal proxy instances of AdvancedERC721Checker. +/// @dev Encodes configuration data for each clone. contract AdvancedERC721CheckerFactory is Factory { + /// @notice Initializes the factory with the AdvancedERC721Checker implementation. constructor() Factory(address(new AdvancedERC721Checker())) {} + /// @notice Deploys a new AdvancedERC721Checker clone. + /// @dev Encodes and appends configuration data for the clone. + /// @param _nftAddress Address of the signup NFT contract. + /// @param _rewardNft Address of the reward NFT contract. + /// @param _baseERC721Checker Address of the base checker contract. + /// @param _minBalance Minimum balance required for validation. + /// @param _minTokenId Minimum token ID for validation. + /// @param _maxTokenId Maximum token ID for validation. function deploy( address _nftAddress, address _rewardNft, @@ -16,7 +27,6 @@ contract AdvancedERC721CheckerFactory is Factory { uint256 _minTokenId, uint256 _maxTokenId ) public { - // 1. Encode. bytes memory data = abi.encode( _nftAddress, _rewardNft, @@ -26,10 +36,8 @@ contract AdvancedERC721CheckerFactory is Factory { _maxTokenId ); - // 2. Deploy. address clone = super._deploy(data); - // 3. Call initialize(). AdvancedERC721Checker(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol index 996de44..84bb645 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721Policy.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.20; import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; -import {AdvancedERC721Checker} from "./AdvancedERC721Checker.sol"; -/// @title AdvancedERC721Policy. -/// @notice Three-phase ERC721 validation policy. -/// @dev Enforces multi-stage checks through AdvancedERC721Checker. +/// @title AdvancedERC721Policy +/// @notice Three-phase policy contract for ERC721 validation. +/// @dev Leverages AdvancedChecker for pre, main, and post validation phases. contract AdvancedERC721Policy is AdvancedPolicy { - /// @notice Returns policy identifier. + /// @notice Returns a unique identifier for the policy. + /// @return The string identifier "AdvancedERC721". function trait() external pure returns (string memory) { return "AdvancedERC721"; } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol index ad63de5..fd53c32 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedERC721PolicyFactory.sol @@ -1,25 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {LibClone} from "solady/src/utils/LibClone.sol"; import {AdvancedERC721Policy} from "./AdvancedERC721Policy.sol"; import {Factory} from "../../core/proxy/Factory.sol"; -/** - * @title AdvancedERC721PolicyFactory - * @notice Example factory for deploying minimal proxies of `AdvancedERC721Policy`. - */ +/// @title AdvancedERC721PolicyFactory +/// @notice Factory for deploying minimal proxy instances of AdvancedERC721Policy. +/// @dev Encodes configuration data for multi-phase policy validation. contract AdvancedERC721PolicyFactory is Factory { + /// @notice Initializes the factory with the AdvancedERC721Policy implementation. constructor() Factory(address(new AdvancedERC721Policy())) {} + /// @notice Deploys a new AdvancedERC721Policy clone. + /// @param _checkerAddr Address of the associated checker contract. + /// @param _skipPre Whether to skip pre-checks. + /// @param _skipPost Whether to skip post-checks. + /// @param _allowMultipleMain Whether multiple main checks are allowed. function deploy(address _checkerAddr, bool _skipPre, bool _skipPost, bool _allowMultipleMain) public { - // 1. Encode. bytes memory data = abi.encode(msg.sender, _checkerAddr, _skipPre, _skipPost, _allowMultipleMain); - // 2. Deploy. address clone = super._deploy(data); - // 3. Call `initialize()`. AdvancedERC721Policy(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol index 487b9de..b46eb1f 100644 --- a/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol +++ b/packages/contracts/contracts/src/test/advanced/AdvancedVoting.sol @@ -4,102 +4,113 @@ pragma solidity ^0.8.20; import {AdvancedPolicy} from "../../core/policy/AdvancedPolicy.sol"; import {Check} from "../../core/interfaces/IAdvancedPolicy.sol"; -/// @title AdvancedVoting. -/// @notice Advanced voting system with NFT-based phases and eligibility verification. -/// @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. +/// @title AdvancedVoting +/// @notice Multi-phase governance system with NFT-based validation. +/// @dev Combines pre, main, and post phases for registration, voting, and eligibility verification. contract AdvancedVoting { - /// @notice Emitted on successful phase completion. - /// @param voter Address that completed the phase. + /// @notice Emitted when a voter registers successfully. + /// @param voter Address of the voter who registered. event Registered(address voter); - /// @param option Selected voting option (0 or 1). + + /// @notice Emitted when a vote is cast successfully. + /// @param voter Address of the voter who cast their vote. + /// @param option The chosen voting option (0 or 1). event Voted(address voter, uint8 option); - /// @param voter Address that met eligibility criteria. + + /// @notice Emitted when a voter is deemed eligible. + /// @param voter Address of the voter who met eligibility criteria. event Eligible(address voter); - /// @notice Validation error states. - /// @dev Thrown when phase requirements not met. - error NotRegistered(); // Pre-check (registration) not completed. - error NotVoted(); // Main check (voting) not completed. - error AlreadyEligible(); // Post check (eligibility) already verified. - error InvalidOption(); // Vote option out of valid range. - error NotEligible(); // Eligibility criteria not met. + /// @notice Error thrown when a user attempts an action without registering first. + error NotRegistered(); + + /// @notice Error thrown when a user attempts to verify eligibility without voting. + error NotVoted(); + + /// @notice Error thrown when a user tries to verify eligibility more than once. + error AlreadyEligible(); + + /// @notice Error thrown when an invalid voting option is provided. + error InvalidOption(); - /// @notice Policy contract managing multi-phase validation. - /// @dev Handles all NFT-based checks through aggregated verifiers. + /// @notice Error thrown when a user does not meet the eligibility criteria. + error NotEligible(); + + /// @notice Reference to the policy contract enforcing multi-phase validation. AdvancedPolicy public immutable POLICY; - /// @notice Vote tracking per option. - /// @dev Maps option ID (0 or 1) to total votes received. + /// @notice Tracks the vote count for each option (0 or 1). mapping(uint8 => uint256) public voteCounts; - /// @notice Initializes voting system. - /// @param _policy Advanced policy contract with configured verifiers. + /// @notice Constructor to set the policy contract. + /// @param _policy Address of the AdvancedPolicy contract to use for validation. constructor(AdvancedPolicy _policy) { POLICY = _policy; } - /// @notice Registration phase handler. - /// @dev Validates signup NFT ownership using BaseERC721Checker. - /// @param tokenId ID of the signup NFT to validate. - /// @custom:requirements - /// - Token must exist. - /// - Caller must be token owner. - /// - Token ID must be within valid range. - /// @custom:emits Registered when registration succeeds. + /// @notice Registers a user for voting by validating their NFT ownership. + /// @dev Enforces the pre-check phase using the AdvancedPolicy contract. + /// @param tokenId The ID of the NFT used to verify registration eligibility. function register(uint256 tokenId) external { + // Prepare evidence with the tokenId encoded as bytes. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(tokenId); + // Enforce the pre-check phase using the provided policy. POLICY.enforce(msg.sender, _evidence, Check.PRE); + // Emit an event to log the registration. emit Registered(msg.sender); } - /// @notice Voting phase handler. - /// @dev Validates voting power and records vote choice. - /// @param option Binary choice (0 or 1). - /// @custom:requirements - /// - Registration must be completed. - /// - Option must be valid (0 or 1). - /// - Token balance must meet requirements. - /// @custom:emits Voted when vote is recorded. + /// @notice Allows a registered user to cast their vote. + /// @dev Enforces the main-check phase and updates the vote count. + /// @param option The chosen voting option (0 or 1). function vote(uint8 option) external { + // Retrieve the enforcement status of the sender from the policy. (bool pre, , ) = POLICY.enforced(msg.sender); + + // Ensure the user has registered before voting. if (!pre) revert NotRegistered(); + + // Validate that the voting option is within the allowed range. if (option >= 2) revert InvalidOption(); + // Prepare evidence with the chosen option encoded as bytes. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(option); + // Enforce the main-check phase using the policy. POLICY.enforce(msg.sender, _evidence, Check.MAIN); + // Increment the vote count for the chosen option. unchecked { voteCounts[option]++; } + // Emit an event to log the voting action. emit Voted(msg.sender, option); } - /// @notice Eligibility verification phase. - /// @dev Validates completion of governance process and checks eligibility criteria. - /// @custom:requirements - /// - Caller must be registered (passed PRE check). - /// - Caller must have voted (passed MAIN check). - /// - Caller must not be already verified (no POST check). - /// - Caller must meet eligibility criteria (no existing benefits). - /// @custom:emits Eligible when verification succeeds. + /// @notice Verifies a user's eligibility after voting has concluded. + /// @dev Enforces the post-check phase to ensure eligibility criteria are met. function eligible() external { + // Retrieve the enforcement status for all phases. (bool pre, uint8 main, bool post) = POLICY.enforced(msg.sender); + // Ensure the user has completed the registration phase. if (!pre) revert NotRegistered(); + + // Ensure the user has cast at least one vote. if (main == 0) revert NotVoted(); + + // Ensure the user has not already been marked as eligible. if (post) revert AlreadyEligible(); + // Enforce the post-check phase using the policy. POLICY.enforce(msg.sender, new bytes[](1), Check.POST); + // Emit an event to log the eligibility status. emit Eligible(msg.sender); } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol index 1d9c53e..dc1216f 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Checker.sol @@ -4,34 +4,35 @@ pragma solidity ^0.8.20; import {BaseChecker} from "../../core/checker/BaseChecker.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -/// @title BaseERC721Checker. +/// @title BaseERC721Checker /// @notice ERC721 token ownership validator. -/// @dev Extends BaseChecker for NFT ownership verification. +/// @dev Extends BaseChecker to implement NFT ownership validation logic. contract BaseERC721Checker is BaseChecker { - /// @notice NFT contract reference. + /// @notice Address of the ERC721 contract used for ownership validation. IERC721 public nft; + /// @notice Initializes the contract with an ERC721 contract address. + /// @dev Decodes the appended bytes from the clone to set the `nft` address. function _initialize() internal override { - // 1. Call super to handle `_initialized` check. super._initialize(); - // 2. Retrieve appended bytes from the clone. bytes memory data = _getAppendedBytes(); - // 3. Decode as a single address. address nftAddress = abi.decode(data, (address)); - // 4. Store it in our storage variable. nft = IERC721(nftAddress); } - /// @notice Validates token ownership. - /// @param subject Address to check. - /// @param evidence Encoded tokenId. - /// @return True if subject owns token. + /// @notice Validates whether the subject owns a specific NFT. + /// @dev Decodes the token ID from evidence and checks ownership via the ERC721 contract. + /// @param subject Address to validate ownership for. + /// @param evidence Encoded token ID used for validation. + /// @return Boolean indicating whether the subject owns the token. function _check(address subject, bytes[] calldata evidence) internal view override returns (bool) { super._check(subject, evidence); + uint256 tokenId = abi.decode(evidence[0], (uint256)); + return nft.ownerOf(tokenId) == subject; } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol index 3c4a70f..2b50829 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721CheckerFactory.sol @@ -3,19 +3,24 @@ pragma solidity ^0.8.20; import {BaseERC721Checker} from "./BaseERC721Checker.sol"; import {Factory} from "../../core/proxy/Factory.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; +/// @title BaseERC721CheckerFactory +/// @notice Factory contract for deploying minimal proxy instances of BaseERC721Checker. +/// @dev Simplifies deployment of ERC721 checker clones with appended configuration data. contract BaseERC721CheckerFactory is Factory { + /// @notice Initializes the factory with the BaseERC721Checker implementation. constructor() Factory(address(new BaseERC721Checker())) {} + /// @notice Deploys a new BaseERC721Checker clone with the specified NFT contract address. + /// @dev Encodes the NFT contract address as configuration data for the clone. + /// @param _nftAddress Address of the ERC721 contract to validate ownership. function deploy(address _nftAddress) public { - // 1. Encode the address for appending. + // Encode the NFT address for the appended data. bytes memory data = abi.encode(_nftAddress); - // 2. Deploy the clone with appended data. + // Deploy the clone and initialize it with the encoded data. address clone = super._deploy(data); - // 3. Call initialize() so the new clone stores `_nftAddress` in `nft`. BaseERC721Checker(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol index c6cc5a6..591c598 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721Policy.sol @@ -3,15 +3,13 @@ pragma solidity ^0.8.20; import {BasePolicy} from "../../core/policy/BasePolicy.sol"; -/** - * @title BaseERC721Policy - * @notice Policy enforcer for ERC721 token validation, built on top of BasePolicy. - * @dev In a minimal proxy context, we remove the constructor arguments and use `initialize()`. - */ +/// @title BaseERC721Policy +/// @notice Policy contract enforcing NFT-based validation. +/// @dev Extends BasePolicy to add specific behavior for ERC721 token validation. contract BaseERC721Policy is BasePolicy { - /** - * @notice A sample policy identifier. - */ + /// @notice Returns a trait identifier for the policy. + /// @dev Used to identify the policy type. + /// @return The trait string "BaseERC721". function trait() external pure returns (string memory) { return "BaseERC721"; } diff --git a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol index 5441730..916bd49 100644 --- a/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol +++ b/packages/contracts/contracts/src/test/base/BaseERC721PolicyFactory.sol @@ -1,25 +1,25 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {LibClone} from "solady/src/utils/LibClone.sol"; import {BaseERC721Policy} from "./BaseERC721Policy.sol"; import {Factory} from "../../core/proxy/Factory.sol"; -/** - * @title BaseERC721PolicyFactory - * @notice Example factory for deploying minimal proxies of `BaseERC721Policy`. - */ +/// @title BaseERC721PolicyFactory +/// @notice Factory contract for deploying minimal proxy instances of BaseERC721Policy. +/// @dev Simplifies deployment of ERC721 policy clones with appended configuration data. contract BaseERC721PolicyFactory is Factory { + /// @notice Initializes the factory with the BaseERC721Policy implementation. constructor() Factory(address(new BaseERC721Policy())) {} + /// @notice Deploys a new BaseERC721Policy clone with the specified checker address. + /// @dev Encodes the checker address and caller as configuration data for the clone. + /// @param _checkerAddr Address of the ERC721 checker to use for validation. function deploy(address _checkerAddr) public { - // 1. Encode. + // Encode the caller (owner) and checker address for appended data. bytes memory data = abi.encode(msg.sender, _checkerAddr); - // 2. Deploy the clone with appended data. address clone = super._deploy(data); - // 3. Call `initialize()` so the clone sets up its owner (the factory) + checker reference. BaseERC721Policy(clone).initialize(); } } diff --git a/packages/contracts/contracts/src/test/base/BaseVoting.sol b/packages/contracts/contracts/src/test/base/BaseVoting.sol index 4e50c80..a6d76b7 100644 --- a/packages/contracts/contracts/src/test/base/BaseVoting.sol +++ b/packages/contracts/contracts/src/test/base/BaseVoting.sol @@ -3,66 +3,67 @@ pragma solidity ^0.8.20; import {BaseERC721Policy} from "./BaseERC721Policy.sol"; -/// @title BaseVoting. -/// @notice Simple voting system with NFT-based access control. -/// @dev Implements basic voting functionality with two phases: -/// 1. Registration: Validates NFT ownership. -/// 2. Voting: Records validated votes. +/// @title BaseVoting +/// @notice Simple NFT-based voting system. +/// @dev Implements a basic two-phase voting system (registration and voting) with access control enforced by NFTs. contract BaseVoting { - /// @notice Emitted on successful registration/voting. + /// @notice Emitted when a voter successfully registers. + /// @param voter Address of the registered voter. event Registered(address voter); + + /// @notice Emitted when a voter successfully casts a vote. + /// @param voter Address of the voter. + /// @param option The option the voter chose. event Voted(address voter, uint8 option); - /// @notice System error conditions. + /// @notice Error thrown when a user attempts to vote without registering. error NotRegistered(); + + /// @notice Error thrown when a user attempts to vote more than once. error AlreadyVoted(); + + /// @notice Error thrown when a user attempts to vote with an invalid option. error InvalidOption(); - /// @notice Policy contract for NFT validation. + /// @notice Policy contract enforcing NFT-based registration. BaseERC721Policy public immutable POLICY; - /// @dev Maps voter address => voting status. + /// @dev Tracks whether an address has voted. mapping(address => bool) public hasVoted; - /// @dev Maps option ID => vote count. + + /// @dev Tracks the number of votes for each option. mapping(uint8 => uint256) public voteCounts; - /// @notice Sets up voting system. - /// @param _policy Contract for voter validation. + /// @notice Initializes the voting system with a specific policy contract. + /// @param _policy Address of the policy contract enforcing access control. constructor(BaseERC721Policy _policy) { POLICY = _policy; } - /// @notice Register using NFT ownership proof. - /// @dev Enforces NFT ownership check through policy. - /// @param tokenId NFT used for registration. - /// @custom:requirements Caller must own the NFT with tokenId. - /// @custom:emits Registered on successful registration. + /// @notice Registers a voter based on NFT ownership. + /// @dev Enforces ownership validation via the policy contract. + /// @param tokenId Token ID of the NFT used for validation. function register(uint256 tokenId) external { - // Encode token ID for policy verification. + // Encode the token ID for policy validation. bytes[] memory _evidence = new bytes[](1); _evidence[0] = abi.encode(tokenId); - // Verify NFT ownership. + // Enforce NFT ownership validation. POLICY.enforce(msg.sender, _evidence); emit Registered(msg.sender); } - /// @notice Cast vote after registration. - /// @dev Updates vote counts if validation passes. - /// @param option Vote choice (0 or 1). - /// @custom:requirements - /// - Caller must be registered. - /// - Caller must not have voted. - /// - Option must be valid (0 or 1). - /// @custom:emits Voted on successful vote cast. + /// @notice Casts a vote after successful registration. + /// @dev Validates voter registration and option validity before recording the vote. + /// @param option The chosen voting option (0 or 1). function vote(uint8 option) external { - // Verify registration and voting status. + // Check registration and voting status. if (!POLICY.enforced(msg.sender)) revert NotRegistered(); if (hasVoted[msg.sender]) revert AlreadyVoted(); if (option >= 2) revert InvalidOption(); - // Record vote. + // Record the vote. hasVoted[msg.sender] = true; voteCounts[option]++; diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol new file mode 100644 index 0000000..772501d --- /dev/null +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AdvancedERC721CheckerFactory} from "../advanced/AdvancedERC721CheckerFactory.sol"; + +/// @title AdvancedERC721CheckerFactoryHarness +/// @notice Test harness exposing internal methods of `AdvancedERC721CheckerFactory` for testing purposes. +/// @dev Inherits `AdvancedERC721CheckerFactory` and provides external methods for accessing internal logic. +contract AdvancedERC721CheckerFactoryHarness is AdvancedERC721CheckerFactory { + /// @notice Test exposure for `_deploy` method. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. + function exposed__deploy(bytes memory data) external returns (address clone) { + return _deploy(data); + } +} diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol index 5ffb4e7..6efe6ec 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol @@ -3,56 +3,50 @@ pragma solidity ^0.8.20; import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; -// @todo refactoring -/// @title AdvancedERC721CheckerHarness. -/// @notice Test harness exposing internal methods of AdvancedERC721Checker. -/// @dev Inherits AdvancedERC721Checker and exposes protected methods for testing. + +/// @title AdvancedERC721CheckerHarness +/// @notice Test harness exposing internal methods of `AdvancedERC721Checker` for testing purposes. +/// @dev Inherits `AdvancedERC721Checker` and provides external methods for accessing internal logic. contract AdvancedERC721CheckerHarness is AdvancedERC721Checker { - /// @notice Initializes test harness with checker configuration. - /// @param _verifiers Array of addresses for existing verification contracts. - /// @param _minBalance Minimum token balance required. - /// @param _minTokenId Minimum valid token ID. - /// @param _maxTokenId Maximum valid token ID. - // constructor( - // address[] memory _verifiers, - // uint256 _minBalance, - // uint256 _minTokenId, - // uint256 _maxTokenId - // ) AdvancedERC721Checker(_verifiers, _minBalance, _minTokenId, _maxTokenId) {} - /// @notice Test exposure for _check method. + /// @notice Test exposure for `_initialize` method. + function exposed__initialize() external { + _initialize(); + } + + /// @notice Test exposure for `_check` method. + /// @dev Allows testing of the generic validation logic. /// @param subject Address to validate. /// @param evidence Validation data. - /// @param checkType Type of check to perform. - /// @return Validation result. - // function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { - // return _check(subject, evidence, checkType); - // } - /// @notice Test exposure for _checkPre method. + /// @param checkType Type of check to perform (PRE, MAIN, POST). + /// @return Boolean indicating whether validation passed. + function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { + return _check(subject, evidence, checkType); + } + + /// @notice Test exposure for `_checkPre` method. + /// @dev Allows testing of the pre-condition validation logic. /// @param subject Address to validate. /// @param evidence Validation data. - /// @return Pre-check validation result. - // function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { - // return _checkPre(subject, evidence); - // } - /// @notice Test exposure for _checkMain method. + /// @return Boolean indicating whether pre-check validation passed. + function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { + return _checkPre(subject, evidence); + } + + /// @notice Test exposure for `_checkMain` method. + /// @dev Allows testing of the main validation logic. /// @param subject Address to validate. /// @param evidence Validation data. - /// @return Main validation result. - // function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { - // return _checkMain(subject, evidence); - // } - /// @notice Test exposure for _checkPost method. + /// @return Boolean indicating whether main validation passed. + function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { + return _checkMain(subject, evidence); + } + + /// @notice Test exposure for `_checkPost` method. + /// @dev Allows testing of the post-condition validation logic. /// @param subject Address to validate. /// @param evidence Validation data. - /// @return Post-check validation result. - // function exposed__checkPost(address subject, bytes[] calldata evidence) public view returns (bool) { - // return _checkPost(subject, evidence); - // } - /// @notice Test exposure for _getVerifierAtIndex method. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - // function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { - // return _getVerifierAtIndex(index); - // } + /// @return Boolean indicating whether post-check validation passed. + function exposed__checkPost(address subject, bytes[] calldata evidence) public view returns (bool) { + return _checkPost(subject, evidence); + } } diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol new file mode 100644 index 0000000..6befd7b --- /dev/null +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AdvancedERC721PolicyFactory} from "../advanced/AdvancedERC721PolicyFactory.sol"; + +/// @title AdvancedERC721PolicyFactoryHarness +/// @notice Test harness exposing internal methods of `AdvancedERC721PolicyFactory` for testing purposes. +/// @dev Inherits `AdvancedERC721PolicyFactory` and provides external methods for accessing internal logic. +contract AdvancedERC721PolicyFactoryHarness is AdvancedERC721PolicyFactory { + /// @notice Test exposure for `_deploy` method. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. + function exposed__deploy(bytes memory data) external returns (address clone) { + return _deploy(data); + } +} diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol index b93a530..0c3478a 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol @@ -2,25 +2,23 @@ pragma solidity ^0.8.20; import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol"; -import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; -// @todo refactoring -/// @title AdvancedERC721PolicyHarness. -/// @notice Test harness for AdvancedERC721Policy internal methods. +/// @title AdvancedERC721PolicyHarness +/// @notice Test harness for `AdvancedERC721Policy` to expose internal methods for testing. +/// @dev Inherits `AdvancedERC721Policy` and allows testing of protected methods. contract AdvancedERC721PolicyHarness is AdvancedERC721Policy { - /// @notice Initializes test harness. - // constructor( - // AdvancedERC721Checker _checker, - // bool _skipPre, - // bool _skipPost, - // bool _allowMultipleMain - // ) AdvancedERC721Policy(_checker, _skipPre, _skipPost, _allowMultipleMain) {} - /// @notice Test exposure for _enforce method. + /// @notice Test exposure for the `_initialize` method. + function exposed__initialize() external { + _initialize(); + } + + /// @notice Test exposure for the `_enforce` method. + /// @dev Allows testing of the internal enforcement logic for a specific phase. /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Check type to enforce. - // function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { - // _enforce(subject, evidence, checkType); - // } + /// @param evidence Validation data for the specified check type. + /// @param checkType Check phase to enforce (PRE, MAIN, POST). + function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { + _enforce(subject, evidence, checkType); + } } diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol new file mode 100644 index 0000000..3ea4d90 --- /dev/null +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseERC721CheckerFactory} from "../base/BaseERC721CheckerFactory.sol"; + +/// @title BaseERC721CheckerHarness +/// @notice Test harness exposing internal methods of `BaseERC721CheckerFactory` for testing purposes. +/// @dev Inherits `BaseERC721CheckerFactory` and provides external methods for accessing internal logic. +contract BaseERC721CheckerFactoryHarness is BaseERC721CheckerFactory { + /// @notice Test exposure for `_deploy` method. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. + function exposed__deploy(bytes memory data) external returns (address clone) { + return _deploy(data); + } +} diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol index 67e64bc..3bdf92a 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol @@ -3,25 +3,21 @@ pragma solidity ^0.8.20; import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; -// @todo refactoring -/// @title BaseERC721CheckerHarness. -/// @notice Test harness for BaseERC721Checker internal methods. +/// @title BaseERC721CheckerHarness +/// @notice Test harness exposing internal methods of `BaseERC721Checker` for testing purposes. +/// @dev Inherits `BaseERC721Checker` and provides external methods for accessing internal logic. contract BaseERC721CheckerHarness is BaseERC721Checker { - /// @notice Initializes test harness with NFT contract. - /// @param _verifiers Array of addresses for existing verification contracts. - // constructor(address[] memory _verifiers) BaseERC721Checker(_verifiers) {} - /// @notice Test exposure for _check method. + /// @notice Test exposure for `_initialize` method. + function exposed__initialize() external { + _initialize(); + } + + /// @notice Test exposure for `_check` method. + /// @dev Allows testing of the generic validation logic. /// @param subject Address to validate. /// @param evidence Validation data. - /// @return Validation result. - // function exposed__check(address subject, bytes[] calldata evidence) public view returns (bool) { - // return _check(subject, evidence); - // } - /// @notice Test exposure for _getVerifierAtIndex method. - /// @param index The index of the verifier in the array. - /// @return The address of the verifier at the specified index. - /// @custom:throws VerifierNotFound if no address have been specified at given index. - // function exposed__getVerifierAtIndex(uint256 index) public view returns (address) { - // return _getVerifierAtIndex(index); - // } + /// @return Boolean indicating whether validation passed. + function exposed__check(address subject, bytes[] calldata evidence) public view returns (bool) { + return _check(subject, evidence); + } } diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol new file mode 100644 index 0000000..ba2237c --- /dev/null +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {BaseERC721PolicyFactory} from "../base/BaseERC721PolicyFactory.sol"; + +/// @title BaseERC721PolicyFactoryHarness +/// @notice Test harness exposing internal methods of `BaseERC721PolicyFactory` for testing purposes. +/// @dev Inherits `BaseERC721PolicyFactory` and provides external methods for accessing internal logic. +contract BaseERC721PolicyFactoryHarness is BaseERC721PolicyFactory { + /// @notice Test exposure for `_deploy` method. + /// @param data Initialization data to append to the clone. + /// @return clone Address of the deployed clone contract. + function exposed__deploy(bytes memory data) external returns (address clone) { + return _deploy(data); + } +} diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol index be79160..4c45092 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol @@ -2,18 +2,21 @@ pragma solidity ^0.8.20; import {BaseERC721Policy} from "../base/BaseERC721Policy.sol"; -import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; -// @todo refactoring -/// @title BaseERC721PolicyHarness. -/// @notice Test harness for BaseERC721Policy internal methods. +/// @title BaseERC721PolicyHarness +/// @notice Test harness for `BaseERC721Policy` to expose internal methods for testing. +/// @dev Inherits `BaseERC721Policy` and allows testing of protected methods. contract BaseERC721PolicyHarness is BaseERC721Policy { - /// @notice Initializes test harness with checker. - // constructor(BaseERC721Checker _checker) BaseERC721Policy(_checker) {} - /// @notice Test exposure for _enforce method. + /// @notice Test exposure for the `_initialize` method. + function exposed__initialize() external { + _initialize(); + } + + /// @notice Test exposure for the `_enforce` method. + /// @dev Allows testing of the internal enforcement logic for a specific phase. /// @param subject Address to validate. - /// @param evidence Validation data. - // function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { - // _enforce(subject, evidence); - // } + /// @param evidence Validation data for the specified check type. + function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { + _enforce(subject, evidence); + } } From d37fe2994cc3d567db558b132ede036eaeaf680d Mon Sep 17 00:00:00 2001 From: 0xjei Date: Tue, 28 Jan 2025 19:33:11 +0100 Subject: [PATCH 09/12] style(contracts): better folder org for harnesses --- .../wrappers/AdvancedERC721CheckerFactoryHarness.sol | 2 +- .../{ => advanced}/wrappers/AdvancedERC721CheckerHarness.sol | 4 ++-- .../wrappers/AdvancedERC721PolicyFactoryHarness.sol | 2 +- .../{ => advanced}/wrappers/AdvancedERC721PolicyHarness.sol | 4 ++-- .../{ => base}/wrappers/BaseERC721CheckerFactoryHarness.sol | 2 +- .../src/test/{ => base}/wrappers/BaseERC721CheckerHarness.sol | 2 +- .../{ => base}/wrappers/BaseERC721PolicyFactoryHarness.sol | 2 +- .../src/test/{ => base}/wrappers/BaseERC721PolicyHarness.sol | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) rename packages/contracts/contracts/src/test/{ => advanced}/wrappers/AdvancedERC721CheckerFactoryHarness.sol (88%) rename packages/contracts/contracts/src/test/{ => advanced}/wrappers/AdvancedERC721CheckerHarness.sol (94%) rename packages/contracts/contracts/src/test/{ => advanced}/wrappers/AdvancedERC721PolicyFactoryHarness.sol (88%) rename packages/contracts/contracts/src/test/{ => advanced}/wrappers/AdvancedERC721PolicyHarness.sol (87%) rename packages/contracts/contracts/src/test/{ => base}/wrappers/BaseERC721CheckerFactoryHarness.sol (89%) rename packages/contracts/contracts/src/test/{ => base}/wrappers/BaseERC721CheckerHarness.sol (93%) rename packages/contracts/contracts/src/test/{ => base}/wrappers/BaseERC721PolicyFactoryHarness.sol (89%) rename packages/contracts/contracts/src/test/{ => base}/wrappers/BaseERC721PolicyHarness.sol (93%) diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol similarity index 88% rename from packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol rename to packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol index 772501d..4ac804b 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerFactoryHarness.sol +++ b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedERC721CheckerFactory} from "../advanced/AdvancedERC721CheckerFactory.sol"; +import {AdvancedERC721CheckerFactory} from "../AdvancedERC721CheckerFactory.sol"; /// @title AdvancedERC721CheckerFactoryHarness /// @notice Test harness exposing internal methods of `AdvancedERC721CheckerFactory` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol similarity index 94% rename from packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol rename to packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol index 6efe6ec..9912167 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedERC721Checker} from "../advanced/AdvancedERC721Checker.sol"; -import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; +import {AdvancedERC721Checker} from "../AdvancedERC721Checker.sol"; +import {Check} from "../../../core/interfaces/IAdvancedChecker.sol"; /// @title AdvancedERC721CheckerHarness /// @notice Test harness exposing internal methods of `AdvancedERC721Checker` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol similarity index 88% rename from packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol rename to packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol index 6befd7b..cad3e86 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyFactoryHarness.sol +++ b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedERC721PolicyFactory} from "../advanced/AdvancedERC721PolicyFactory.sol"; +import {AdvancedERC721PolicyFactory} from "../AdvancedERC721PolicyFactory.sol"; /// @title AdvancedERC721PolicyFactoryHarness /// @notice Test harness exposing internal methods of `AdvancedERC721PolicyFactory` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol similarity index 87% rename from packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol rename to packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol index 0c3478a..9c605a2 100644 --- a/packages/contracts/contracts/src/test/wrappers/AdvancedERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {AdvancedERC721Policy} from "../advanced/AdvancedERC721Policy.sol"; -import {Check} from "../../core/interfaces/IAdvancedChecker.sol"; +import {AdvancedERC721Policy} from "../AdvancedERC721Policy.sol"; +import {Check} from "../../../core/interfaces/IAdvancedChecker.sol"; /// @title AdvancedERC721PolicyHarness /// @notice Test harness for `AdvancedERC721Policy` to expose internal methods for testing. diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol similarity index 89% rename from packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol rename to packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol index 3ea4d90..8e6d63d 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerFactoryHarness.sol +++ b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BaseERC721CheckerFactory} from "../base/BaseERC721CheckerFactory.sol"; +import {BaseERC721CheckerFactory} from "../BaseERC721CheckerFactory.sol"; /// @title BaseERC721CheckerHarness /// @notice Test harness exposing internal methods of `BaseERC721CheckerFactory` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol similarity index 93% rename from packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol rename to packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol index 3bdf92a..f4f4a8f 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721CheckerHarness.sol +++ b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BaseERC721Checker} from "../base/BaseERC721Checker.sol"; +import {BaseERC721Checker} from "../BaseERC721Checker.sol"; /// @title BaseERC721CheckerHarness /// @notice Test harness exposing internal methods of `BaseERC721Checker` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol similarity index 89% rename from packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol rename to packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol index ba2237c..d168685 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyFactoryHarness.sol +++ b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BaseERC721PolicyFactory} from "../base/BaseERC721PolicyFactory.sol"; +import {BaseERC721PolicyFactory} from "../BaseERC721PolicyFactory.sol"; /// @title BaseERC721PolicyFactoryHarness /// @notice Test harness exposing internal methods of `BaseERC721PolicyFactory` for testing purposes. diff --git a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol similarity index 93% rename from packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol rename to packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol index 4c45092..0f52c24 100644 --- a/packages/contracts/contracts/src/test/wrappers/BaseERC721PolicyHarness.sol +++ b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; -import {BaseERC721Policy} from "../base/BaseERC721Policy.sol"; +import {BaseERC721Policy} from "../BaseERC721Policy.sol"; /// @title BaseERC721PolicyHarness /// @notice Test harness for `BaseERC721Policy` to expose internal methods for testing. From 5f0ef094c6dbc6b87e32398ff610dcf6397af341 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 29 Jan 2025 16:20:42 +0100 Subject: [PATCH 10/12] refactor(contracts): remove harnesses since every internal and protected method can be reached --- .../AdvancedERC721CheckerFactoryHarness.sol | 16 ------ .../wrappers/AdvancedERC721CheckerHarness.sol | 52 ------------------- .../AdvancedERC721PolicyFactoryHarness.sol | 16 ------ .../wrappers/AdvancedERC721PolicyHarness.sol | 24 --------- .../BaseERC721CheckerFactoryHarness.sol | 16 ------ .../wrappers/BaseERC721CheckerHarness.sol | 23 -------- .../BaseERC721PolicyFactoryHarness.sol | 16 ------ .../base/wrappers/BaseERC721PolicyHarness.sol | 22 -------- 8 files changed, 185 deletions(-) delete mode 100644 packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol delete mode 100644 packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol delete mode 100644 packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol delete mode 100644 packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol delete mode 100644 packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol delete mode 100644 packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol delete mode 100644 packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol delete mode 100644 packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol diff --git a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol deleted file mode 100644 index 4ac804b..0000000 --- a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerFactoryHarness.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721CheckerFactory} from "../AdvancedERC721CheckerFactory.sol"; - -/// @title AdvancedERC721CheckerFactoryHarness -/// @notice Test harness exposing internal methods of `AdvancedERC721CheckerFactory` for testing purposes. -/// @dev Inherits `AdvancedERC721CheckerFactory` and provides external methods for accessing internal logic. -contract AdvancedERC721CheckerFactoryHarness is AdvancedERC721CheckerFactory { - /// @notice Test exposure for `_deploy` method. - /// @param data Initialization data to append to the clone. - /// @return clone Address of the deployed clone contract. - function exposed__deploy(bytes memory data) external returns (address clone) { - return _deploy(data); - } -} diff --git a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol deleted file mode 100644 index 9912167..0000000 --- a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721CheckerHarness.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721Checker} from "../AdvancedERC721Checker.sol"; -import {Check} from "../../../core/interfaces/IAdvancedChecker.sol"; - -/// @title AdvancedERC721CheckerHarness -/// @notice Test harness exposing internal methods of `AdvancedERC721Checker` for testing purposes. -/// @dev Inherits `AdvancedERC721Checker` and provides external methods for accessing internal logic. -contract AdvancedERC721CheckerHarness is AdvancedERC721Checker { - /// @notice Test exposure for `_initialize` method. - function exposed__initialize() external { - _initialize(); - } - - /// @notice Test exposure for `_check` method. - /// @dev Allows testing of the generic validation logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @param checkType Type of check to perform (PRE, MAIN, POST). - /// @return Boolean indicating whether validation passed. - function exposed__check(address subject, bytes[] calldata evidence, Check checkType) public view returns (bool) { - return _check(subject, evidence, checkType); - } - - /// @notice Test exposure for `_checkPre` method. - /// @dev Allows testing of the pre-condition validation logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Boolean indicating whether pre-check validation passed. - function exposed__checkPre(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPre(subject, evidence); - } - - /// @notice Test exposure for `_checkMain` method. - /// @dev Allows testing of the main validation logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Boolean indicating whether main validation passed. - function exposed__checkMain(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkMain(subject, evidence); - } - - /// @notice Test exposure for `_checkPost` method. - /// @dev Allows testing of the post-condition validation logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Boolean indicating whether post-check validation passed. - function exposed__checkPost(address subject, bytes[] calldata evidence) public view returns (bool) { - return _checkPost(subject, evidence); - } -} diff --git a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol deleted file mode 100644 index cad3e86..0000000 --- a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyFactoryHarness.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721PolicyFactory} from "../AdvancedERC721PolicyFactory.sol"; - -/// @title AdvancedERC721PolicyFactoryHarness -/// @notice Test harness exposing internal methods of `AdvancedERC721PolicyFactory` for testing purposes. -/// @dev Inherits `AdvancedERC721PolicyFactory` and provides external methods for accessing internal logic. -contract AdvancedERC721PolicyFactoryHarness is AdvancedERC721PolicyFactory { - /// @notice Test exposure for `_deploy` method. - /// @param data Initialization data to append to the clone. - /// @return clone Address of the deployed clone contract. - function exposed__deploy(bytes memory data) external returns (address clone) { - return _deploy(data); - } -} diff --git a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol deleted file mode 100644 index 9c605a2..0000000 --- a/packages/contracts/contracts/src/test/advanced/wrappers/AdvancedERC721PolicyHarness.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {AdvancedERC721Policy} from "../AdvancedERC721Policy.sol"; -import {Check} from "../../../core/interfaces/IAdvancedChecker.sol"; - -/// @title AdvancedERC721PolicyHarness -/// @notice Test harness for `AdvancedERC721Policy` to expose internal methods for testing. -/// @dev Inherits `AdvancedERC721Policy` and allows testing of protected methods. -contract AdvancedERC721PolicyHarness is AdvancedERC721Policy { - /// @notice Test exposure for the `_initialize` method. - function exposed__initialize() external { - _initialize(); - } - - /// @notice Test exposure for the `_enforce` method. - /// @dev Allows testing of the internal enforcement logic for a specific phase. - /// @param subject Address to validate. - /// @param evidence Validation data for the specified check type. - /// @param checkType Check phase to enforce (PRE, MAIN, POST). - function exposed__enforce(address subject, bytes[] calldata evidence, Check checkType) public onlyTarget { - _enforce(subject, evidence, checkType); - } -} diff --git a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol deleted file mode 100644 index 8e6d63d..0000000 --- a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerFactoryHarness.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721CheckerFactory} from "../BaseERC721CheckerFactory.sol"; - -/// @title BaseERC721CheckerHarness -/// @notice Test harness exposing internal methods of `BaseERC721CheckerFactory` for testing purposes. -/// @dev Inherits `BaseERC721CheckerFactory` and provides external methods for accessing internal logic. -contract BaseERC721CheckerFactoryHarness is BaseERC721CheckerFactory { - /// @notice Test exposure for `_deploy` method. - /// @param data Initialization data to append to the clone. - /// @return clone Address of the deployed clone contract. - function exposed__deploy(bytes memory data) external returns (address clone) { - return _deploy(data); - } -} diff --git a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol deleted file mode 100644 index f4f4a8f..0000000 --- a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721CheckerHarness.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721Checker} from "../BaseERC721Checker.sol"; - -/// @title BaseERC721CheckerHarness -/// @notice Test harness exposing internal methods of `BaseERC721Checker` for testing purposes. -/// @dev Inherits `BaseERC721Checker` and provides external methods for accessing internal logic. -contract BaseERC721CheckerHarness is BaseERC721Checker { - /// @notice Test exposure for `_initialize` method. - function exposed__initialize() external { - _initialize(); - } - - /// @notice Test exposure for `_check` method. - /// @dev Allows testing of the generic validation logic. - /// @param subject Address to validate. - /// @param evidence Validation data. - /// @return Boolean indicating whether validation passed. - function exposed__check(address subject, bytes[] calldata evidence) public view returns (bool) { - return _check(subject, evidence); - } -} diff --git a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol deleted file mode 100644 index d168685..0000000 --- a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyFactoryHarness.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721PolicyFactory} from "../BaseERC721PolicyFactory.sol"; - -/// @title BaseERC721PolicyFactoryHarness -/// @notice Test harness exposing internal methods of `BaseERC721PolicyFactory` for testing purposes. -/// @dev Inherits `BaseERC721PolicyFactory` and provides external methods for accessing internal logic. -contract BaseERC721PolicyFactoryHarness is BaseERC721PolicyFactory { - /// @notice Test exposure for `_deploy` method. - /// @param data Initialization data to append to the clone. - /// @return clone Address of the deployed clone contract. - function exposed__deploy(bytes memory data) external returns (address clone) { - return _deploy(data); - } -} diff --git a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol b/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol deleted file mode 100644 index 0f52c24..0000000 --- a/packages/contracts/contracts/src/test/base/wrappers/BaseERC721PolicyHarness.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {BaseERC721Policy} from "../BaseERC721Policy.sol"; - -/// @title BaseERC721PolicyHarness -/// @notice Test harness for `BaseERC721Policy` to expose internal methods for testing. -/// @dev Inherits `BaseERC721Policy` and allows testing of protected methods. -contract BaseERC721PolicyHarness is BaseERC721Policy { - /// @notice Test exposure for the `_initialize` method. - function exposed__initialize() external { - _initialize(); - } - - /// @notice Test exposure for the `_enforce` method. - /// @dev Allows testing of the internal enforcement logic for a specific phase. - /// @param subject Address to validate. - /// @param evidence Validation data for the specified check type. - function exposed__enforce(address subject, bytes[] calldata evidence) public onlyTarget { - _enforce(subject, evidence); - } -} From b5fe47c55cc6a870d0f1d3b0dc5ceaf941491519 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 29 Jan 2025 17:45:39 +0100 Subject: [PATCH 11/12] test(contracts): refactoring of solidity tests and reached 100% code coverage --- .../contracts/src/core/policy/BasePolicy.sol | 1 - .../contracts/src/core/proxy/Clone.sol | 6 +- .../contracts/src/test/Advanced.t.sol | 1407 ++++++----------- .../contracts/contracts/src/test/Base.t.sol | 577 +++---- 4 files changed, 709 insertions(+), 1282 deletions(-) diff --git a/packages/contracts/contracts/src/core/policy/BasePolicy.sol b/packages/contracts/contracts/src/core/policy/BasePolicy.sol index ca848ef..4af62d2 100644 --- a/packages/contracts/contracts/src/core/policy/BasePolicy.sol +++ b/packages/contracts/contracts/src/core/policy/BasePolicy.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.20; import {IBasePolicy} from "../interfaces/IBasePolicy.sol"; import {Policy} from "./Policy.sol"; import {BaseChecker} from "../checker/BaseChecker.sol"; -import {LibClone} from "solady/src/utils/LibClone.sol"; /// @title BasePolicy /// @notice Abstract base contract for implementing custom policies using a BaseChecker. diff --git a/packages/contracts/contracts/src/core/proxy/Clone.sol b/packages/contracts/contracts/src/core/proxy/Clone.sol index 63a9321..1a5d2c4 100644 --- a/packages/contracts/contracts/src/core/proxy/Clone.sol +++ b/packages/contracts/contracts/src/core/proxy/Clone.sol @@ -10,7 +10,7 @@ import {LibClone} from "solady/src/utils/LibClone.sol"; abstract contract Clone is IClone { /// @notice Tracks whether the clone has been initialized. /// @dev Prevents re-initialization through the `_initialize` function. - bool private _initialized; + bool public initialized; /// @notice Initializes the clone. /// @dev Calls the internal `_initialize` function to set up the clone. @@ -30,8 +30,8 @@ abstract contract Clone is IClone { /// @dev Must be overridden by derived contracts to implement custom initialization logic. /// Reverts if the clone has already been initialized. function _initialize() internal virtual { - if (_initialized) revert AlreadyInitialized(); - _initialized = true; + if (initialized) revert AlreadyInitialized(); + initialized = true; } /// @notice Internal function to retrieve appended arguments from the clone. diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index e784aed..212628e 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -16,10 +16,8 @@ import {IPolicy} from "../core/interfaces/IPolicy.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Check} from "../core/interfaces/IAdvancedChecker.sol"; - -// @todo add harnesses -// @todo add tests for factories + harnesses -// @todo add edge cases +import {IClone} from "../core/interfaces/IClone.sol"; +import {IAdvancedPolicy} from "../core/interfaces/IAdvancedPolicy.sol"; contract AdvancedChecker is Test { event CloneDeployed(address indexed clone); @@ -40,20 +38,19 @@ contract AdvancedChecker is Test { function setUp() public virtual { vm.startPrank(deployer); + signupNft = new NFT(); rewardNft = new NFT(); - signupNft.mint(subject); + baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - // For first deploy - capture the event vm.recordLogs(); baseFactory.deploy(address(signupNft)); Vm.Log[] memory entries = vm.getRecordedLogs(); address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - // For second deploy - capture the event vm.recordLogs(); advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); entries = vm.getRecordedLogs(); @@ -61,239 +58,94 @@ contract AdvancedChecker is Test { advancedChecker = AdvancedERC721Checker(advancedClone); evidence[0] = abi.encode(0); + vm.stopPrank(); } - function test_simple() public { - bool result = advancedChecker.check(subject, evidence, Check.PRE); - assertTrue(result, "Expected subject to own token 0"); + function test_factory_deployAndInitialize() public view { + assertEq(advancedChecker.initialized(), true); } - // @todo refactoring - // 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))); - // advancedChecker.check(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_checkPre_whenCallerNotOwner_returnsFalse() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(!advancedChecker.check(notOwner, evidence, Check.PRE)); - - // vm.stopPrank(); - // } - - // function test_checkPre_whenValid_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedChecker.check(subject, evidence, Check.PRE)); - - // vm.stopPrank(); - // } - - // function test_checkMain_whenCallerHasNoTokens_returnsFalse() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(!advancedChecker.check(notOwner, evidence, Check.MAIN)); - - // vm.stopPrank(); - // } - - // function test_checkMain_whenCallerHasTokens_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedChecker.check(subject, evidence, Check.MAIN)); - - // vm.stopPrank(); - // } - - // function test_checkPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - // vm.startPrank(target); - - // rewardNft.mint(subject); - - // assert(!advancedChecker.check(subject, evidence, Check.POST)); - - // vm.stopPrank(); - // } - - // function test_checkPost_whenValid_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedChecker.check(subject, evidence, Check.POST)); - - // vm.stopPrank(); - // } - - // function test_checkerPre_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_checkerPre_whenCallerNotOwner_returnsFalse() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.PRE)); - - // vm.stopPrank(); - // } - - // function test_checkerPre_whenValid_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.PRE)); - - // vm.stopPrank(); - // } - - // function test_checkerMain_whenCallerHasNoTokens_returnsFalse() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(!advancedCheckerHarness.exposed__check(notOwner, evidence, Check.MAIN)); - - // vm.stopPrank(); - // } - - // function test_checkerMain_whenCallerHasTokens_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.MAIN)); - - // vm.stopPrank(); - // } - - // function test_checkerPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - // vm.startPrank(target); - - // rewardNft.mint(subject); - - // assert(!advancedCheckerHarness.check(subject, evidence, Check.POST)); - - // vm.stopPrank(); - // } - - // function test_checkerPost_whenValid_succeeds() public { - // vm.startPrank(target); - - // signupNft.mint(subject); - - // assert(advancedCheckerHarness.exposed__check(subject, evidence, Check.POST)); + function test_checker_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + advancedChecker.initialize(); + } - // vm.stopPrank(); - // } + function test_checker_getAppendedBytes() public { + assertEq( + advancedChecker.getAppendedBytes(), + abi.encode(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10) + ); + } - // function test_internalPre_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(target); + function test_checkPre_whenTokenDoesNotExist_reverts() public { + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - // advancedCheckerHarness.exposed__checkPre(subject, wrongEvidence); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + advancedChecker.check(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalPre_whenCallerNotOwner_returnsFalse() public { - // vm.startPrank(target); + function test_checkPre_whenCallerNotOwner_returnsFalse() public { + vm.startPrank(target); - // signupNft.mint(subject); + signupNft.mint(subject); - // assert(!advancedCheckerHarness.exposed__checkPre(notOwner, evidence)); + assert(!advancedChecker.check(notOwner, evidence, Check.PRE)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalPre_whenValid_succeeds() public { - // vm.startPrank(target); + function test_checkPre_whenValid_succeeds() public { + vm.startPrank(target); - // signupNft.mint(subject); + signupNft.mint(subject); - // assert(advancedCheckerHarness.exposed__checkPre(subject, evidence)); + assert(advancedChecker.check(subject, evidence, Check.PRE)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalMain_whenCallerHasNoTokens_returnsFalse() public { - // vm.startPrank(target); + function test_checkMain_whenCallerHasNoTokens_returnsFalse() public { + vm.startPrank(target); - // signupNft.mint(subject); + signupNft.mint(subject); - // assert(!advancedCheckerHarness.exposed__checkMain(notOwner, evidence)); + assert(!advancedChecker.check(notOwner, evidence, Check.MAIN)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalMain_whenCallerHasTokens_succeeds() public { - // vm.startPrank(target); + function test_checkMain_whenCallerHasTokens_succeeds() public { + vm.startPrank(target); - // signupNft.mint(subject); + signupNft.mint(subject); - // assert(advancedCheckerHarness.exposed__checkMain(subject, evidence)); + assert(advancedChecker.check(subject, evidence, Check.MAIN)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { - // vm.startPrank(target); + function test_checkPost_whenCallerBalanceGreaterThanZero_returnsFalse() public { + vm.startPrank(target); - // rewardNft.mint(subject); + rewardNft.mint(subject); - // assert(!advancedCheckerHarness.exposed__checkPost(subject, evidence)); + assert(!advancedChecker.check(subject, evidence, Check.POST)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_internalPost_whenValid_succeeds() public { - // vm.startPrank(target); + function test_checkPost_whenValid_succeeds() public { + vm.startPrank(target); - // signupNft.mint(subject); + signupNft.mint(subject); - // assert(advancedCheckerHarness.exposed__checkPost(subject, evidence)); + assert(advancedChecker.check(subject, evidence, Check.POST)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } contract AdvancedPolicy is Test { @@ -307,6 +159,7 @@ contract AdvancedPolicy is Test { AdvancedERC721Checker internal advancedChecker; AdvancedERC721CheckerFactory internal advancedFactory; AdvancedERC721Policy internal policy; + AdvancedERC721Policy internal policySkipped; AdvancedERC721PolicyFactory internal policyFactory; address public deployer = vm.addr(0x1); @@ -323,728 +176,440 @@ contract AdvancedPolicy is Test { signupNft = new NFT(); rewardNft = new NFT(); - signupNft.mint(subject); - baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - // For first deploy - capture the event vm.recordLogs(); baseFactory.deploy(address(signupNft)); Vm.Log[] memory entries = vm.getRecordedLogs(); address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - // For second deploy - capture the event vm.recordLogs(); advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); entries = vm.getRecordedLogs(); address advancedClone = address(uint160(uint256(entries[0].topics[1]))); advancedChecker = AdvancedERC721Checker(advancedClone); - advancedChecker = AdvancedERC721Checker(advancedClone); - policyFactory = new AdvancedERC721PolicyFactory(); - // For first deploy - capture the event vm.recordLogs(); - policyFactory.deploy(address(advancedChecker), false, false, false); + policyFactory.deploy(address(advancedChecker), false, false, true); entries = vm.getRecordedLogs(); address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = AdvancedERC721Policy(policyClone); + vm.recordLogs(); + policyFactory.deploy(address(advancedChecker), true, true, false); + entries = vm.getRecordedLogs(); + address policyCloneSkipped = address(uint160(uint256(entries[0].topics[1]))); + policySkipped = AdvancedERC721Policy(policyCloneSkipped); + evidence[0] = abi.encode(0); wrongEvidence[0] = abi.encode(1); vm.stopPrank(); } - function test_simple() public { - assertEq(policy.owner(), address(deployer)); - assertEq(policy.trait(), "AdvancedERC721"); + function test_factory_deployAndInitialize() public view { + assertEq(policy.initialized(), true); + assertEq(policySkipped.initialized(), true); } - // function test_trait_returnsCorrectValue() public view { - // assertEq(policy.trait(), "AdvancedERC721"); - // } - - // function test_setTarget_whenCallerNotOwner_reverts() public { - // vm.startPrank(notOwner); - - // vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); - // policy.setTarget(target); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenZeroAddress_reverts() public { - // vm.startPrank(deployer); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); - // policy.setTarget(address(0)); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenValid_succeeds() public { - // vm.startPrank(deployer); - - // vm.expectEmit(true, true, true, true); - // emit TargetSet(target); - - // policy.setTarget(target); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenAlreadySet_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); - // policy.setTarget(target); - - // vm.stopPrank(); - // } + function test_policy_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policy.initialize(); - // function test_enforcePre_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(subject); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policy.enforce(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforcePre_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // policy.enforce(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforcePre_whenChecksSkipped_reverts() public { - // vm.startPrank(deployer); - - // policySkipped.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); - // policySkipped.enforce(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforcePre_whenCheckFails_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policy.enforce(notOwner, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforcePre_whenValid_succeeds() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.PRE); - - // policy.enforce(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforcePre_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // policy.enforce(subject, evidence, Check.PRE); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policy.enforce(subject, evidence, Check.PRE); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(subject); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenCheckFails_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenPreCheckMissing_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenValid_succeeds() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // policy.enforce(subject, evidence, Check.PRE); - - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); - - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenMultipleValid_succeeds() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // policy.enforce(subject, evidence, Check.PRE); - - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); - - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); - - // policy.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforceMain_whenMultipleNotAllowed_reverts() public { - // vm.startPrank(deployer); - - // policySkipped.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // policySkipped.enforce(subject, evidence, Check.MAIN); - - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); - // policySkipped.enforce(subject, evidence, Check.MAIN); - - // vm.stopPrank(); - // } - - // function test_enforcePost_whenPreCheckMissing_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - // policy.enforce(subject, evidence, Check.PRE); - - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); - // policy.enforce(subject, evidence, Check.POST); - - // vm.stopPrank(); - // } - - // function test_enforcePost_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(subject); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policy.enforce(subject, evidence, Check.POST); - - // vm.stopPrank(); - // } - - // function test_enforcePost_whenChecksSkipped_reverts() public { - // vm.startPrank(deployer); - - // policySkipped.setTarget(target); - // signupNft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // policySkipped.enforce(subject, evidence, Check.MAIN); - - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); - // policySkipped.enforce(subject, evidence, Check.POST); - - // vm.stopPrank(); - // } - - // function test_enforcePost_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policySkipped.initialize(); + } - // policy.setTarget(target); - // signupNft.mint(subject); + function test_policy_getAppendedBytes() public { + assertEq( + policy.getAppendedBytes(), + abi.encode(address(deployer), address(advancedChecker), false, false, true) + ); + assertEq( + policySkipped.getAppendedBytes(), + abi.encode(address(deployer), address(advancedChecker), true, true, false) + ); + } - // vm.stopPrank(); + function test_trait_returnsCorrectValue() public view { + assertEq(policy.trait(), "AdvancedERC721"); + } - // vm.startPrank(target); + function test_setTarget_whenCallerNotOwner_reverts() public { + vm.startPrank(notOwner); - // policy.enforce(subject, evidence, Check.PRE); - // policy.enforce(subject, evidence, Check.MAIN); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); + policy.setTarget(target); - // rewardNft.mint(subject); + vm.stopPrank(); + } - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policy.enforce(subject, evidence, Check.POST); + function test_setTarget_whenZeroAddress_reverts() public { + vm.startPrank(deployer); - // vm.stopPrank(); - // } + vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); + policy.setTarget(address(0)); - // function test_enforcePost_whenValid_succeeds() public { - // vm.startPrank(deployer); + vm.stopPrank(); + } - // policy.setTarget(target); - // signupNft.mint(subject); + function test_setTarget_whenValid_succeeds() public { + vm.startPrank(deployer); - // vm.stopPrank(); + vm.expectEmit(true, true, true, true); + emit TargetSet(target); - // vm.startPrank(target); + policy.setTarget(target); - // policy.enforce(subject, evidence, Check.PRE); - // policy.enforce(subject, evidence, Check.MAIN); + vm.stopPrank(); + } - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.POST); + function test_setTarget_whenAlreadySet_reverts() public { + vm.startPrank(deployer); - // policy.enforce(subject, evidence, Check.POST); + policy.setTarget(target); - // vm.stopPrank(); - // } + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); + policy.setTarget(target); - // function test_enforcePost_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); + vm.stopPrank(); + } - // policy.setTarget(target); - // signupNft.mint(subject); + function test_enforcePre_whenCallerNotTarget_reverts() public { + vm.startPrank(deployer); - // vm.stopPrank(); + policy.setTarget(target); - // vm.startPrank(target); + vm.stopPrank(); - // policy.enforce(subject, evidence, Check.PRE); - // policy.enforce(subject, evidence, Check.MAIN); - // policy.enforce(subject, evidence, Check.POST); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policy.enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + policy.enforce(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePreInternal_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); + function test_enforcePre_whenTokenDoesNotExist_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + policy.enforce(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePreInternal_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); + function test_enforcePre_whenChecksSkipped_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policySkipped.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); + policySkipped.enforce(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePreInternal_whenChecksSkipped_reverts() public { - // vm.startPrank(deployer); + function test_enforcePre_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policyHarnessSkipped.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPreCheckWhenSkipped.selector)); - // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.PRE); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + policy.enforce(notOwner, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePreInternal_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_enforcePre_whenValid_succeeds() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policyHarness.exposed__enforce(notOwner, evidence, Check.PRE); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence, Check.PRE); - // vm.stopPrank(); - // } + policy.enforce(subject, evidence, Check.PRE); - // function test_enforcePreInternal_whenValid_succeeds() public { - // vm.startPrank(deployer); + vm.stopPrank(); + } - // policyHarness.setTarget(target); - // signupNft.mint(subject); + function test_enforcePre_whenAlreadyEnforced_reverts() public { + vm.startPrank(deployer); - // vm.stopPrank(); + policy.setTarget(target); + signupNft.mint(subject); - // vm.startPrank(target); + vm.stopPrank(); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.PRE); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + policy.enforce(subject, evidence, Check.PRE); - // function test_enforcePreInternal_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); + vm.stopPrank(); + } - // policyHarness.setTarget(target); - // signupNft.mint(subject); + function test_enforceMain_whenCallerNotTarget_reverts() public { + vm.startPrank(deployer); - // vm.stopPrank(); + policy.setTarget(target); - // vm.startPrank(target); + vm.stopPrank(); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + policy.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceMainInternal_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); + function test_enforceMain_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.PRE); - // vm.stopPrank(); - // } + vm.stopPrank(); - // function test_enforceMainInternal_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + vm.startPrank(subject); - // policyHarness.setTarget(target); + signupNft.transferFrom(subject, target, 0); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + policy.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceMainInternal_whenPreCheckMissing_reverts() public { - // vm.startPrank(deployer); + function test_enforceMain_whenPreCheckMissing_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.PreCheckNotEnforced.selector)); + policy.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceMainInternal_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_enforceMain_whenValid_succeeds() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.PRE); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence, Check.MAIN); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceMainInternal_whenMultipleValid_succeeds() public { - // vm.startPrank(deployer); + function test_enforceMain_whenMultipleValid_succeeds() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.PRE); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence, Check.MAIN); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.MAIN); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.MAIN); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence, Check.MAIN); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceMainInternal_whenMultipleNotAllowed_reverts() public { - // vm.startPrank(deployer); + function test_enforceMain_whenMultipleNotAllowed_reverts() public { + vm.startPrank(deployer); - // policyHarnessSkipped.setTarget(target); - // signupNft.mint(subject); + policySkipped.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + policySkipped.enforce(subject, evidence, Check.MAIN); - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); - // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckAlreadyEnforced.selector)); + policySkipped.enforce(subject, evidence, Check.MAIN); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenPreCheckMissing_reverts() public { - // vm.startPrank(deployer); + function test_enforcePost_whenPreCheckMissing_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); + vm.startPrank(target); + policy.enforce(subject, evidence, Check.PRE); - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.MainCheckNotEnforced.selector)); + policy.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); + function test_enforcePost_whenCallerNotTarget_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + policy.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenChecksSkipped_reverts() public { - // vm.startPrank(deployer); + function test_enforcePost_whenChecksSkipped_reverts() public { + vm.startPrank(deployer); - // policyHarnessSkipped.setTarget(target); - // signupNft.mint(subject); + policySkipped.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.MAIN); + policySkipped.enforce(subject, evidence, Check.MAIN); - // vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); - // policyHarnessSkipped.exposed__enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IAdvancedPolicy.CannotPostCheckWhenSkipped.selector)); + policySkipped.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_enforcePost_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.MAIN); - // rewardNft.mint(subject); + rewardNft.mint(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + policy.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_enforcePost_whenValid_succeeds() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.MAIN); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence, Check.POST); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence, Check.POST); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + policy.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforcePostInternal_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); + function test_enforcePost_whenAlreadyEnforced_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // signupNft.mint(subject); + policy.setTarget(target); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence, Check.PRE); - // policyHarness.exposed__enforce(subject, evidence, Check.MAIN); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + policy.enforce(subject, evidence, Check.PRE); + policy.enforce(subject, evidence, Check.MAIN); + policy.enforce(subject, evidence, Check.POST); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policyHarness.exposed__enforce(subject, evidence, Check.POST); + vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + policy.enforce(subject, evidence, Check.POST); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } contract Voting is Test { @@ -1079,27 +644,22 @@ contract Voting is Test { baseFactory = new BaseERC721CheckerFactory(); advancedFactory = new AdvancedERC721CheckerFactory(); - // For first deploy - capture the event vm.recordLogs(); baseFactory.deploy(address(signupNft)); Vm.Log[] memory entries = vm.getRecordedLogs(); address baseClone = address(uint160(uint256(entries[0].topics[1]))); baseChecker = BaseERC721Checker(baseClone); - // For second deploy - capture the event vm.recordLogs(); advancedFactory.deploy(address(signupNft), address(rewardNft), address(baseChecker), 1, 0, 10); entries = vm.getRecordedLogs(); address advancedClone = address(uint160(uint256(entries[0].topics[1]))); advancedChecker = AdvancedERC721Checker(advancedClone); - advancedChecker = AdvancedERC721Checker(advancedClone); - policyFactory = new AdvancedERC721PolicyFactory(); - // For first deploy - capture the event vm.recordLogs(); - policyFactory.deploy(address(advancedChecker), false, false, false); + policyFactory.deploy(address(advancedChecker), false, false, true); entries = vm.getRecordedLogs(); address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = AdvancedERC721Policy(policyClone); @@ -1128,262 +688,265 @@ contract Voting is Test { vm.stopPrank(); } - // @todo refactoring + function test_voting_deployed() public view { + assertEq(address(voting.POLICY()), address(policy)); + assertEq(voting.voteCounts(0), 0); + } - // function test_register_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); + function test_register_whenCallerNotTarget_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(deployer); - // signupNft.mint(subject); + policy.setTarget(deployer); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(notOwner); + vm.startPrank(notOwner); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // voting.register(0); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + voting.register(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_register_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); + function test_register_whenTokenDoesNotExist_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - // voting.register(1); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); + voting.register(1); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_register_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_register_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(notOwner); + vm.startPrank(notOwner); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // voting.register(0); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + voting.register(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_register_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_register_whenValid_succeeds() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectEmit(true, true, true, true); - // emit Registered(subject); + vm.expectEmit(true, true, true, true); + emit Registered(subject); - // voting.register(0); + voting.register(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_register_whenAlreadyRegistered_reverts() public { - // vm.startPrank(deployer); + function test_register_whenAlreadyRegistered_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); + voting.register(0); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // voting.register(0); + vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + voting.register(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_vote_whenNotRegistered_reverts() public { - // vm.startPrank(deployer); + function test_vote_whenNotRegistered_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); - // voting.vote(0); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); + voting.vote(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_vote_whenInvalidOption_reverts() public { - // vm.startPrank(deployer); + function test_vote_whenInvalidOption_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); - // voting.register(0); + vm.startPrank(subject); + voting.register(0); - // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.InvalidOption.selector)); - // voting.vote(3); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.InvalidOption.selector)); + voting.vote(3); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_vote_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_vote_whenValid_succeeds() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); - // voting.register(0); + vm.startPrank(subject); + voting.register(0); - // vm.expectEmit(true, true, true, true); - // emit Voted(subject, 0); + vm.expectEmit(true, true, true, true); + emit Voted(subject, 0); - // voting.vote(0); + voting.vote(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_vote_whenMultipleValid_succeeds() public { - // vm.startPrank(deployer); + function test_vote_whenMultipleValid_succeeds() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); - // voting.vote(0); + voting.register(0); + voting.vote(0); - // vm.expectEmit(true, true, true, true); - // emit Voted(subject, 0); - // voting.vote(0); + vm.expectEmit(true, true, true, true); + emit Voted(subject, 0); + voting.vote(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_eligible_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_eligible_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); - // signupNft.mint(notOwner); + policy.setTarget(address(voting)); + signupNft.mint(subject); + signupNft.mint(notOwner); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(notOwner); + vm.startPrank(notOwner); - // voting.register(1); - // voting.vote(0); + voting.register(1); + voting.vote(0); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); - // voting.vote(0); + voting.register(0); + voting.vote(0); - // rewardNft.mint(subject); + rewardNft.mint(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // voting.eligible(); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + voting.eligible(); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_eligible_whenNotRegistered_reverts() public { - // vm.startPrank(deployer); + function test_eligible_whenNotRegistered_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); - // voting.eligible(); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotRegistered.selector)); + voting.eligible(); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_eligible_whenNotVoted_reverts() public { - // vm.startPrank(deployer); + function test_eligible_whenNotVoted_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); - // voting.register(0); + vm.startPrank(subject); + voting.register(0); - // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotVoted.selector)); - // voting.eligible(); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.NotVoted.selector)); + voting.eligible(); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_eligible_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_eligible_whenValid_succeeds() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); - // voting.vote(0); + voting.register(0); + voting.vote(0); - // vm.expectEmit(true, true, true, true); - // emit Eligible(subject); + vm.expectEmit(true, true, true, true); + emit Eligible(subject); - // voting.eligible(); + voting.eligible(); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_eligible_whenAlreadyEligible_reverts() public { - // vm.startPrank(deployer); + function test_eligible_whenAlreadyEligible_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // signupNft.mint(subject); + policy.setTarget(address(voting)); + signupNft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); - // voting.vote(0); - // voting.eligible(); + voting.register(0); + voting.vote(0); + voting.eligible(); - // vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.AlreadyEligible.selector)); - // voting.eligible(); + vm.expectRevert(abi.encodeWithSelector(AdvancedVoting.AlreadyEligible.selector)); + voting.eligible(); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } diff --git a/packages/contracts/contracts/src/test/Base.t.sol b/packages/contracts/contracts/src/test/Base.t.sol index 473c453..4609ae9 100644 --- a/packages/contracts/contracts/src/test/Base.t.sol +++ b/packages/contracts/contracts/src/test/Base.t.sol @@ -9,13 +9,10 @@ import {BaseERC721PolicyFactory} from "./base/BaseERC721PolicyFactory.sol"; import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; import {BaseVoting} from "./base/BaseVoting.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; +import {IClone} from "../core/interfaces/IClone.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -// @todo add harnesses -// @todo add tests for factories + harnesses -// @todo add edge cases - contract BaseChecker is Test { NFT internal nft; BaseERC721Checker internal checker; @@ -25,19 +22,15 @@ contract BaseChecker is Test { address public target = vm.addr(0x2); address public subject = vm.addr(0x3); address public notOwner = vm.addr(0x4); - - address[] internal verifiers; bytes[] public evidence = new bytes[](1); function setUp() public { vm.startPrank(deployer); nft = new NFT(); - nft.mint(subject); factory = new BaseERC721CheckerFactory(); - // For first deploy - capture the event vm.recordLogs(); factory.deploy(address(nft)); Vm.Log[] memory entries = vm.getRecordedLogs(); @@ -49,87 +42,47 @@ contract BaseChecker is Test { vm.stopPrank(); } - function test_simple() public { - bool result = checker.check(subject, evidence); - assertTrue(result, "Expected subject to own token 0"); + function test_factory_deployAndInitialize() public view { + assertEq(checker.initialized(), true); } - /// @todo refactoring - // 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); - - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // checkerHarness.exposed__check(subject, evidence); - - // vm.stopPrank(); - // } - - // function test_checker_whenCallerNotOwner_returnsFalse() public { - // vm.startPrank(target); - - // nft.mint(subject); - - // assert(!checkerHarness.exposed__check(notOwner, evidence)); - - // vm.stopPrank(); - // } - - // function test_checker_whenCallerIsOwner_succeeds() public { - // vm.startPrank(target); - - // nft.mint(subject); - - // assert(checkerHarness.exposed__check(subject, evidence)); + function test_checker_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + checker.initialize(); + } - // vm.stopPrank(); - // } + function test_checker_getAppendedBytes() public { + assertEq(checker.getAppendedBytes(), abi.encode(address(nft))); + } - // function test_checkerExternal_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(target); + function test_checker_whenTokenDoesNotExist_reverts() public { + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // checker.check(subject, evidence); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + checker.check(subject, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_checkerExternal_whenCallerNotOwner_returnsFalse() public { - // vm.startPrank(target); + function test_checker_whenCallerNotOwner_returnsFalse() public { + vm.startPrank(target); - // nft.mint(subject); + nft.mint(subject); - // assert(!checker.check(notOwner, evidence)); + assert(!checker.check(notOwner, evidence)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_checkerExternal_whenCallerIsOwner_succeeds() public { - // vm.startPrank(target); + function test_checker_whenCallerIsOwner_succeeds() public { + vm.startPrank(target); - // nft.mint(subject); + nft.mint(subject); - // assert(checker.check(subject, evidence)); + assert(checker.check(subject, evidence)); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } contract BasePolicy is Test { @@ -153,19 +106,16 @@ contract BasePolicy is Test { vm.startPrank(deployer); nft = new NFT(); - nft.mint(subject); checkerFactory = new BaseERC721CheckerFactory(); policyFactory = new BaseERC721PolicyFactory(); - // For first deploy - capture the event vm.recordLogs(); checkerFactory.deploy(address(nft)); Vm.Log[] memory entries = vm.getRecordedLogs(); address checkerClone = address(uint160(uint256(entries[0].topics[1]))); checker = BaseERC721Checker(checkerClone); - // For second deploy - capture the event vm.recordLogs(); policyFactory.deploy(address(checker)); entries = vm.getRecordedLogs(); @@ -177,228 +127,154 @@ contract BasePolicy is Test { vm.stopPrank(); } - function test_simple() public { - assertEq(policy.owner(), address(deployer)); - assertEq(policy.trait(), "BaseERC721"); + function test_factory_deployAndInitialize() public view { + assertEq(policy.initialized(), true); } - // @todo refactoring - // function test_trait_returnsCorrectValue() public view { - // assertEq(policy.trait(), "BaseERC721"); - // } - - // function test_getTarget_returnsExpectedAddress() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // assertEq(policy.getTarget(), target); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenCallerNotOwner_reverts() public { - // vm.startPrank(notOwner); - - // vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); - // policy.setTarget(target); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenZeroAddress_reverts() public { - // vm.startPrank(deployer); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); - // policy.setTarget(address(0)); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenValidAddress_succeeds() public { - // vm.startPrank(deployer); - - // vm.expectEmit(true, true, true, true); - // emit TargetSet(target); - - // policy.setTarget(target); - - // vm.stopPrank(); - // } - - // function test_setTarget_whenAlreadySet_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); - // policy.setTarget(target); - - // vm.stopPrank(); - // } - - // function test_enforce_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(subject); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policy.enforce(subject, evidence); - - // vm.stopPrank(); - // } - - // function test_enforce_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(target); - - // vm.stopPrank(); - - // vm.startPrank(target); - - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // policy.enforce(subject, evidence); - - // vm.stopPrank(); - // } + function test_policy_whenAlreadyInitialized_reverts() public { + vm.expectRevert(abi.encodeWithSelector(IClone.AlreadyInitialized.selector)); + policy.initialize(); + } - // function test_enforce_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_policy_getAppendedBytes() public { + assertEq(policy.getAppendedBytes(), abi.encode(address(deployer), address(checker))); + } - // policy.setTarget(target); - // nft.mint(subject); + function test_policy_trait_returnsCorrectValue() public view { + assertEq(policy.trait(), "BaseERC721"); + } - // vm.stopPrank(); + function test_policy_target_returnsExpectedAddress() public { + vm.startPrank(deployer); - // vm.startPrank(target); + policy.setTarget(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policy.enforce(notOwner, evidence); + assertEq(policy.target(), target); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforce_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_policy_setTarget_whenCallerNotOwner_reverts() public { + vm.startPrank(notOwner); - // policy.setTarget(target); - // nft.mint(subject); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); + } - // vm.startPrank(target); + function test_policy_setTarget_whenZeroAddress_reverts() public { + vm.startPrank(deployer); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence); + vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); + policy.setTarget(address(0)); - // policy.enforce(subject, evidence); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_policy_setTarget_whenValidAddress_succeeds() public { + vm.startPrank(deployer); - // function test_enforce_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); + vm.expectEmit(true, true, true, true); + emit TargetSet(target); - // policy.setTarget(target); - // nft.mint(subject); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); + } - // vm.startPrank(target); + function test_policy_setTarget_whenAlreadySet_reverts() public { + vm.startPrank(deployer); - // policy.enforce(subject, evidence); + policy.setTarget(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policy.enforce(subject, evidence); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetAlreadySet.selector)); + policy.setTarget(target); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceInternal_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); + function test_policy_enforce_whenCallerNotTarget_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // policyHarness.exposed__enforce(subject, evidence); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); + policy.enforce(subject, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceInternal_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); + function test_policy_enforce_whenTokenDoesNotExist_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); + policy.setTarget(target); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); - // policyHarness.exposed__enforce(subject, evidence); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0))); + policy.enforce(subject, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceInternal_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + function test_policy_enforce_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // nft.mint(subject); + policy.setTarget(target); + nft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // policyHarness.exposed__enforce(notOwner, evidence); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + policy.enforce(notOwner, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceInternal_whenValid_succeeds() public { - // vm.startPrank(deployer); + function test_policy_enforce_whenValid_succeeds() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // nft.mint(subject); + policy.setTarget(target); + nft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // vm.expectEmit(true, true, true, true); - // emit Enforced(subject, target, evidence); + vm.expectEmit(true, true, true, true); + emit Enforced(subject, target, evidence); - // policyHarness.exposed__enforce(subject, evidence); + policy.enforce(subject, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_enforceInternal_whenAlreadyEnforced_reverts() public { - // vm.startPrank(deployer); + function test_policy_enforce_whenAlreadyEnforced_reverts() public { + vm.startPrank(deployer); - // policyHarness.setTarget(target); - // nft.mint(subject); + policy.setTarget(target); + nft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(target); + vm.startPrank(target); - // policyHarness.exposed__enforce(subject, evidence); + policy.enforce(subject, evidence); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // policyHarness.exposed__enforce(subject, evidence); + vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + policy.enforce(subject, evidence); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } contract Voting is Test { @@ -420,199 +296,188 @@ contract Voting is Test { vm.startPrank(deployer); nft = new NFT(); - nft.mint(subject); checkerFactory = new BaseERC721CheckerFactory(); policyFactory = new BaseERC721PolicyFactory(); - // For first deploy - capture the event vm.recordLogs(); checkerFactory.deploy(address(nft)); Vm.Log[] memory entries = vm.getRecordedLogs(); address checkerClone = address(uint160(uint256(entries[0].topics[1]))); checker = BaseERC721Checker(checkerClone); - // For second deploy - capture the event vm.recordLogs(); policyFactory.deploy(address(checker)); entries = vm.getRecordedLogs(); address policyClone = address(uint160(uint256(entries[0].topics[1]))); policy = BaseERC721Policy(policyClone); + voting = new BaseVoting(policy); vm.stopPrank(); } - function test_simple() public { + function test_voting_deployed() public view { assertEq(address(voting.POLICY()), address(policy)); + assertEq(voting.hasVoted(subject), false); + assertEq(voting.voteCounts(0), 0); + } + function test_register_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); - policy.setTarget(address(voting)); + policy.setTarget(deployer); nft.mint(subject); vm.stopPrank(); - vm.startPrank(subject); + vm.startPrank(notOwner); + vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); voting.register(0); + vm.stopPrank(); } - // @todo refactoring - - // function test_register_whenCallerNotTarget_reverts() public { - // vm.startPrank(deployer); - - // policy.setTarget(deployer); - // nft.mint(subject); - - // vm.stopPrank(); - - // vm.startPrank(notOwner); - - // vm.expectRevert(abi.encodeWithSelector(IPolicy.TargetOnly.selector)); - // voting.register(0); - - // vm.stopPrank(); - // } + function test_register_whenTokenDoesNotExist_reverts() public { + vm.startPrank(deployer); - // function test_register_whenTokenDoesNotExist_reverts() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); - // vm.startPrank(subject); + vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); + voting.register(1); - // vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(1))); - // voting.register(1); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_register_whenCheckFails_reverts() public { + vm.startPrank(deployer); - // function test_register_whenCheckFails_reverts() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(notOwner); - // vm.startPrank(notOwner); + vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); + voting.register(0); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.UnsuccessfulCheck.selector)); - // voting.register(0); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_register_whenValid_succeeds() public { + vm.startPrank(deployer); - // function test_register_whenValid_succeeds() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); - // vm.startPrank(subject); + vm.expectEmit(true, true, true, true); + emit Registered(subject); - // vm.expectEmit(true, true, true, true); - // emit Registered(subject); + voting.register(0); - // voting.register(0); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_register_whenAlreadyRegistered_reverts() public { + vm.startPrank(deployer); - // function test_register_whenAlreadyRegistered_reverts() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); - // vm.startPrank(subject); + voting.register(0); - // voting.register(0); + vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); + voting.register(0); - // vm.expectRevert(abi.encodeWithSelector(IPolicy.AlreadyEnforced.selector)); - // voting.register(0); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_vote_whenNotRegistered_reverts() public { + vm.startPrank(deployer); - // function test_vote_whenNotRegistered_reverts() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); - // vm.startPrank(subject); + vm.expectRevert(abi.encodeWithSelector(BaseVoting.NotRegistered.selector)); + voting.vote(0); - // vm.expectRevert(abi.encodeWithSelector(BaseVoting.NotRegistered.selector)); - // voting.vote(0); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_vote_whenInvalidOption_reverts() public { + vm.startPrank(deployer); - // function test_vote_whenInvalidOption_reverts() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); + voting.register(0); - // vm.startPrank(subject); - // voting.register(0); + vm.expectRevert(abi.encodeWithSelector(BaseVoting.InvalidOption.selector)); + voting.vote(3); - // vm.expectRevert(abi.encodeWithSelector(BaseVoting.InvalidOption.selector)); - // voting.vote(3); + vm.stopPrank(); + } - // vm.stopPrank(); - // } + function test_vote_whenValid_succeeds() public { + vm.startPrank(deployer); - // function test_vote_whenValid_succeeds() public { - // vm.startPrank(deployer); + policy.setTarget(address(voting)); + nft.mint(subject); - // policy.setTarget(address(voting)); - // nft.mint(subject); + vm.stopPrank(); - // vm.stopPrank(); + vm.startPrank(subject); + voting.register(0); - // vm.startPrank(subject); - // voting.register(0); + vm.expectEmit(true, true, true, true); + emit Voted(subject, 0); - // vm.expectEmit(true, true, true, true); - // emit Voted(subject, 0); + voting.vote(0); - // voting.vote(0); + assertEq(voting.hasVoted(subject), true); + assertEq(voting.voteCounts(0), 1); - // vm.stopPrank(); - // } + vm.stopPrank(); + } - // function test_vote_whenAlreadyVoted_reverts() public { - // vm.startPrank(deployer); + function test_vote_whenAlreadyVoted_reverts() public { + vm.startPrank(deployer); - // policy.setTarget(address(voting)); - // nft.mint(subject); + policy.setTarget(address(voting)); + nft.mint(subject); - // vm.stopPrank(); + vm.stopPrank(); - // vm.startPrank(subject); + vm.startPrank(subject); - // voting.register(0); - // voting.vote(0); + voting.register(0); + voting.vote(0); - // vm.expectRevert(abi.encodeWithSelector(BaseVoting.AlreadyVoted.selector)); - // voting.vote(0); + vm.expectRevert(abi.encodeWithSelector(BaseVoting.AlreadyVoted.selector)); + voting.vote(0); - // vm.stopPrank(); - // } + vm.stopPrank(); + } } From 578f5d6fa8d79df30ccf6d1e2cca4524dbe5b7e2 Mon Sep 17 00:00:00 2001 From: 0xjei Date: Wed, 29 Jan 2025 23:56:20 +0100 Subject: [PATCH 12/12] test(contracts): update hh tests to reflect contracts changes --- .../contracts/src/test/Advanced.t.sol | 48 +- packages/contracts/test/Advanced.test.ts | 2780 ++++++++--------- packages/contracts/test/Base.test.ts | 1280 ++++---- 3 files changed, 1975 insertions(+), 2133 deletions(-) diff --git a/packages/contracts/contracts/src/test/Advanced.t.sol b/packages/contracts/contracts/src/test/Advanced.t.sol index 212628e..ca23a53 100644 --- a/packages/contracts/contracts/src/test/Advanced.t.sol +++ b/packages/contracts/contracts/src/test/Advanced.t.sol @@ -9,8 +9,6 @@ import {BaseERC721CheckerFactory} from "./base/BaseERC721CheckerFactory.sol"; import {AdvancedERC721CheckerFactory} from "./advanced/AdvancedERC721CheckerFactory.sol"; import {AdvancedERC721Policy} from "./advanced/AdvancedERC721Policy.sol"; import {AdvancedERC721PolicyFactory} from "./advanced/AdvancedERC721PolicyFactory.sol"; -import {BaseERC721Policy} from "./base/BaseERC721Policy.sol"; -import {BaseVoting} from "./base/BaseVoting.sol"; import {AdvancedVoting} from "./advanced/AdvancedVoting.sol"; import {IPolicy} from "../core/interfaces/IPolicy.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; @@ -235,11 +233,11 @@ contract AdvancedPolicy is Test { ); } - function test_trait_returnsCorrectValue() public view { + function test_policy_trait_returnsCorrectValue() public view { assertEq(policy.trait(), "AdvancedERC721"); } - function test_setTarget_whenCallerNotOwner_reverts() public { + function test_policy_setTarget_whenCallerNotOwner_reverts() public { vm.startPrank(notOwner); vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner)); @@ -248,7 +246,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenZeroAddress_reverts() public { + function test_policy_setTarget_whenZeroAddress_reverts() public { vm.startPrank(deployer); vm.expectRevert(abi.encodeWithSelector(IPolicy.ZeroAddress.selector)); @@ -257,7 +255,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenValid_succeeds() public { + function test_policy_setTarget_whenValid_succeeds() public { vm.startPrank(deployer); vm.expectEmit(true, true, true, true); @@ -268,7 +266,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_setTarget_whenAlreadySet_reverts() public { + function test_policy_setTarget_whenAlreadySet_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -279,7 +277,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenCallerNotTarget_reverts() public { + function test_policy_enforcePre_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -294,7 +292,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenTokenDoesNotExist_reverts() public { + function test_policy_enforcePre_whenTokenDoesNotExist_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -309,7 +307,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenChecksSkipped_reverts() public { + function test_policy_enforcePre_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -325,7 +323,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenCheckFails_reverts() public { + function test_policy_enforcePre_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -341,7 +339,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenValid_succeeds() public { + function test_policy_enforcePre_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -359,7 +357,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePre_whenAlreadyEnforced_reverts() public { + function test_policy_enforcePre_whenAlreadyEnforced_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -377,7 +375,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenCallerNotTarget_reverts() public { + function test_policy_enforceMain_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -392,7 +390,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenCheckFails_reverts() public { + function test_policy_enforceMain_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -420,7 +418,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenPreCheckMissing_reverts() public { + function test_policy_enforceMain_whenPreCheckMissing_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -436,7 +434,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenValid_succeeds() public { + function test_policy_enforceMain_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -456,7 +454,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenMultipleValid_succeeds() public { + function test_policy_enforceMain_whenMultipleValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -481,7 +479,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforceMain_whenMultipleNotAllowed_reverts() public { + function test_policy_enforceMain_whenMultipleNotAllowed_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -499,7 +497,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenPreCheckMissing_reverts() public { + function test_policy_enforcePost_whenPreCheckMissing_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -516,7 +514,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenCallerNotTarget_reverts() public { + function test_policy_enforcePost_whenCallerNotTarget_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -531,7 +529,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenChecksSkipped_reverts() public { + function test_policy_enforcePost_whenChecksSkipped_reverts() public { vm.startPrank(deployer); policySkipped.setTarget(target); @@ -549,7 +547,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenCheckFails_reverts() public { + function test_policy_enforcePost_whenCheckFails_reverts() public { vm.startPrank(deployer); policy.setTarget(target); @@ -570,7 +568,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenValid_succeeds() public { + function test_policy_enforcePost_whenValid_succeeds() public { vm.startPrank(deployer); policy.setTarget(target); @@ -591,7 +589,7 @@ contract AdvancedPolicy is Test { vm.stopPrank(); } - function test_enforcePost_whenAlreadyEnforced_reverts() public { + function test_policy_enforcePost_whenAlreadyEnforced_reverts() public { vm.startPrank(deployer); policy.setTarget(target); diff --git a/packages/contracts/test/Advanced.test.ts b/packages/contracts/test/Advanced.test.ts index 5470d7b..0662c57 100644 --- a/packages/contracts/test/Advanced.test.ts +++ b/packages/contracts/test/Advanced.test.ts @@ -1,1483 +1,1297 @@ -// @todo refactoring -// describe("Advanced", () => { -// describe("Checker", () => { -// async function deployAdvancedCheckerFixture() { -// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = -// await ethers.getContractFactory("AdvancedERC721Checker") -// const AdvancedERC721CheckerHarnessFactory: AdvancedERC721CheckerHarness__factory = -// await ethers.getContractFactory("AdvancedERC721CheckerHarness") - -// const signupNft: NFT = await NFTFactory.deploy() -// const rewardNft: NFT = await NFTFactory.deploy() -// const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await signupNft.getAddress() -// ]) -// const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect(deployer).deploy( -// [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], -// 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 signupNft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) -// const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - -// return { -// signupNft, -// rewardNft, -// baseChecker, -// advancedChecker, -// advancedCheckerHarness, -// deployer, -// target, -// subject, -// subjectAddress, -// notOwnerAddress, -// validNFTId, -// invalidNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) - -// expect(advancedChecker).to.not.eq(undefined) -// }) -// }) - -// describe("check", () => { -// describe("pre check", () => { -// it("reverts when evidence invalid", async () => { -// const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await expect( -// advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 0) -// ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") -// }) - -// it("returns false when not owner", async () => { -// const { advancedChecker, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 0)).to.be.equal( -// false -// ) -// }) - -// it("succeeds when valid", async () => { -// const { advancedChecker, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 0)).to.be.equal( -// true -// ) -// }) -// }) -// describe("main check", () => { -// it("returns false when balance insufficient", async () => { -// const { advancedChecker, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect(await advancedChecker.connect(target).check(notOwnerAddress, [validNFTId], 1)).to.be.equal( -// false -// ) -// }) - -// it("succeeds when balance sufficient", async () => { -// const { advancedChecker, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 1)).to.be.equal( -// true -// ) -// }) -// }) -// describe("post check", () => { -// it("reverts when already rewarded", async () => { -// const { rewardNft, advancedChecker, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await rewardNft.mint(subjectAddress) - -// expect(await advancedChecker.connect(target).check(subjectAddress, [invalidNFTId], 2)).to.be.equal( -// false -// ) -// }) - -// it("succeeds when in valid range", async () => { -// const { advancedChecker, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect(await advancedChecker.connect(target).check(subjectAddress, [validNFTId], 2)).to.be.equal( -// true -// ) -// }) -// }) -// }) - -// describe("getVerifierAtIndex", () => { -// it("returns correct verifier address", async () => { -// const { advancedChecker, signupNft } = await loadFixture(deployAdvancedCheckerFixture) -// expect(await advancedChecker.getVerifierAtIndex(0)).to.equal(await signupNft.getAddress()) -// }) - -// it("reverts when index out of bounds", async () => { -// const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) -// await expect(advancedChecker.getVerifierAtIndex(5)).to.be.revertedWithCustomError( -// 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 { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await expect( -// advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId], 0) -// ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") -// }) - -// it("returns false when not owner", async () => { -// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 0) -// ).to.be.equal(false) -// }) - -// it("succeeds when valid", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 0) -// ).to.be.equal(true) -// }) -// }) -// describe("main check", () => { -// it("returns false when balance insufficient", async () => { -// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId], 1) -// ).to.be.equal(false) -// }) - -// it("succeeds when balance sufficient", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 1) -// ).to.be.equal(true) -// }) -// }) -// describe("post check", () => { -// it("reverts when evidence invalid", async () => { -// const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await rewardNft.mint(subjectAddress) - -// expect( -// await advancedCheckerHarness.connect(target).check(subjectAddress, [invalidNFTId], 2) -// ).to.be.equal(false) -// }) - -// it("succeeds when in valid range", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__check(subjectAddress, [validNFTId], 2) -// ).to.be.equal(true) -// }) -// }) -// }) - -// describe("internal checkPre", () => { -// it("reverts when evidence invalid", async () => { -// const { signupNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await expect( -// advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [invalidNFTId]) -// ).to.be.revertedWithCustomError(signupNft, "ERC721NonexistentToken") -// }) - -// it("returns false when not owner", async () => { -// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkPre(notOwnerAddress, [validNFTId]) -// ).to.be.equal(false) -// }) - -// it("succeeds when valid", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkPre(subjectAddress, [validNFTId]) -// ).to.be.equal(true) -// }) -// }) - -// describe("internal checkMain", () => { -// it("returns false when balance insufficient", async () => { -// const { advancedCheckerHarness, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkMain(notOwnerAddress, [validNFTId]) -// ).to.be.equal(false) -// }) - -// it("succeeds when balance sufficient", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkMain(subjectAddress, [validNFTId]) -// ).to.be.equal(true) -// }) -// }) - -// describe("internal checkPost", () => { -// it("reverts when evidence invalid", async () => { -// const { rewardNft, advancedCheckerHarness, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// await rewardNft.mint(subjectAddress) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [invalidNFTId]) -// ).to.be.equal(false) -// }) - -// it("succeeds when in valid range", async () => { -// const { advancedCheckerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedCheckerFixture) - -// expect( -// await advancedCheckerHarness.connect(target).exposed__checkPost(subjectAddress, [validNFTId]) -// ).to.be.equal(true) -// }) -// }) -// }) - -// describe("Policy", () => { -// async function deployAdvancedPolicyFixture() { -// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = -// await ethers.getContractFactory("AdvancedERC721Checker") -// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = -// await ethers.getContractFactory("AdvancedERC721Policy") -// const AdvancedERC721PolicyHarnessFactory: AdvancedERC721PolicyHarness__factory = -// await ethers.getContractFactory("AdvancedERC721PolicyHarness") - -// const signupNft: NFT = await NFTFactory.deploy() -// const rewardNft: NFT = await NFTFactory.deploy() -// const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( -// "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 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 advancedChecker.getAddress(), -// false, -// false, -// true -// ) -// const policySkipped: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( -// await advancedCheckerSkippedPrePostNoMultMain.getAddress(), -// true, -// true, -// false -// ) -// const policyHarness: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( -// deployer -// ).deploy(await advancedChecker.getAddress(), false, false, true) -// const policyHarnessSkipped: AdvancedERC721PolicyHarness = await AdvancedERC721PolicyHarnessFactory.connect( -// deployer -// ).deploy(await advancedCheckerSkippedPrePostNoMultMain.getAddress(), true, true, false) - -// // mint 0 for subject. -// await signupNft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) -// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - -// return { -// signupIERC721Errors, -// rewardIERC721Errors, -// AdvancedERC721PolicyFactory, -// signupNft, -// rewardNft, -// advancedChecker, -// advancedCheckerSkippedPrePostNoMultMain, -// policyHarness, -// policyHarnessSkipped, -// policy, -// policySkipped, -// subject, -// deployer, -// target, -// notOwner, -// subjectAddress, -// notOwnerAddress, -// validEncodedNFTId, -// invalidEncodedNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { policy } = await loadFixture(deployAdvancedPolicyFixture) - -// expect(policy).to.not.eq(undefined) -// }) -// }) - -// describe("trait", () => { -// it("returns correct value", async () => { -// const { policy } = await loadFixture(deployAdvancedPolicyFixture) - -// expect(await policy.trait()).to.be.eq("AdvancedERC721") -// }) -// }) - -// describe("setTarget", () => { -// it("reverts when caller not owner", async () => { -// const { policy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) - -// await expect( -// policy.connect(notOwner).setTarget(await target.getAddress()) -// ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") -// }) - -// it("reverts when zero address", async () => { -// const { policy, deployer } = await loadFixture(deployAdvancedPolicyFixture) - -// await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( -// policy, -// "ZeroAddress" -// ) -// }) - -// it("sets target correctly", async () => { -// const { policy, target, AdvancedERC721PolicyFactory } = await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// const tx = await policy.setTarget(targetAddress) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// target: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.target).to.eq(targetAddress) -// expect(await policy.getTarget()).to.eq(targetAddress) -// }) - -// it("reverts when already set", async () => { -// const { policy, target } = await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(targetAddress) - -// await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") -// }) -// }) - -// describe("enforce", () => { -// describe("pre check", () => { -// it("reverts when caller not target", async () => { -// const { policy, subject, target, subjectAddress } = await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// await expect( -// policy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) -// ).to.be.revertedWithCustomError(policy, "TargetOnly") -// }) - -// it("reverts when evidence invalid", async () => { -// 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(rewardIERC721Errors, "ERC721NonexistentToken") -// }) - -// it("reverts when pre-check skipped", async () => { -// const { policySkipped, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policySkipped.setTarget(await target.getAddress()) - -// await expect( -// policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policySkipped, "CannotPreCheckWhenSkipped") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policy, target, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// expect( -// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") -// }) - -// it("enforces pre-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(targetAddress) - -// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// checkType: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(event.args.checkType).to.eq(0) -// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) -// }) - -// it("reverts when pre already enforced", async () => { -// const { policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - -// await expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") -// }) -// }) - -// describe("main check", () => { -// it("reverts when pre-check missing", async () => { -// const { policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - -// expect( -// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") -// }) - -// it("enforces main-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - -// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// checkType: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(event.args.checkType).to.eq(1) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1) -// }) - -// it("executes multiple mains when allowed", async () => { -// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() -// await policy.setTarget(targetAddress) - -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// checkType: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(event.args.checkType).to.eq(1) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2) -// }) - -// it("executes multiple mains when allowed", async () => { -// const { policySkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policySkipped.setTarget(await target.getAddress()) -// await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// expect( -// policySkipped.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policySkipped, "MainCheckAlreadyEnforced") -// }) -// }) - -// describe("post check", () => { -// it("reverts when pre/main missing", async () => { -// const { policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policy, "PreCheckNotEnforced") - -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) - -// expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policy, "MainCheckNotEnforced") -// }) - -// it("reverts when caller not target", async () => { -// const { policy, subject, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// await expect( -// policy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) -// ).to.be.revertedWithCustomError(policy, "TargetOnly") -// }) - -// it("reverts when already rewarded", async () => { -// const { rewardNft, policy, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// await rewardNft.mint(subjectAddress) - -// await expect( -// policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") -// }) - -// it("reverts when post-check skipped", async () => { -// const { policySkipped, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policySkipped.setTarget(await target.getAddress()) -// await policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// await expect( -// policySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policySkipped, "CannotPostCheckWhenSkipped") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// expect( -// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") -// }) - -// it("enforces post-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(targetAddress) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) - -// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// checkType: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(event.args.checkType).to.eq(2) -// expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) -// }) - -// it("reverts when post already enforced", async () => { -// const { policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policy.setTarget(await target.getAddress()) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) - -// await expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") -// }) -// }) -// }) - -// describe("internal enforce", () => { -// describe("internal pre", () => { -// it("reverts when caller not target", async () => { -// const { policyHarness, subject, target, subjectAddress } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await expect( -// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 0) -// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") -// }) - -// it("reverts when evidence invalid", async () => { -// const { rewardIERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(rewardIERC721Errors, "ERC721NonexistentToken") -// }) - -// it("reverts when pre-check skipped", async () => { -// const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarnessSkipped.setTarget(await target.getAddress()) - -// await expect( -// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPreCheckWhenSkipped") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// expect( -// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") -// }) - -// it("enforces pre-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policyHarness.setTarget(targetAddress) - -// const tx = await policyHarness -// .connect(target) -// .exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect((await policyHarness.enforced(subjectAddress))[0]).to.be.equal(true) -// }) - -// it("reverts when pre already enforced", async () => { -// const { policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - -// await expect( -// policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) -// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") -// }) -// }) - -// describe("_main", () => { -// it("reverts when pre-check missing", async () => { -// const { policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policyHarness, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - -// expect( -// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") -// }) - -// it("enforces main-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - -// const tx = await policyHarness -// .connect(target) -// .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(1) -// }) - -// it("executes multiple mains when allowed", async () => { -// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() -// await policyHarness.setTarget(targetAddress) - -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// const tx = await policyHarness -// .connect(target) -// .exposed__enforce(subjectAddress, [validEncodedNFTId], 1) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect((await policyHarness.enforced(subjectAddress))[1]).to.be.equal(2) -// }) - -// it("executes multiple mains when allowed", async () => { -// const { policyHarnessSkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarnessSkipped.setTarget(await target.getAddress()) -// await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// expect( -// policyHarnessSkipped.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") -// expect( -// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) -// ).to.be.revertedWithCustomError(policyHarnessSkipped, "MainCheckAlreadyEnforced") -// }) -// }) - -// describe("_post", () => { -// it("reverts when pre/main missing", async () => { -// const { policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarness, "PreCheckNotEnforced") - -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) - -// expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarness, "MainCheckNotEnforced") -// }) - -// it("reverts when caller not target", async () => { -// const { policyHarness, subject, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// await expect( -// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash], 2) -// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") -// }) - -// it("reverts when evidence invalid", async () => { -// const { rewardNft, policyHarness, target, subjectAddress, validEncodedNFTId, invalidEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// await rewardNft.mint(subjectAddress) - -// await expect( -// policyHarness.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") -// }) - -// it("reverts when post-check skipped", async () => { -// const { policyHarnessSkipped, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarnessSkipped.setTarget(await target.getAddress()) -// await policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// await expect( -// policyHarnessSkipped.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarnessSkipped, "CannotPostCheckWhenSkipped") -// }) - -// it("reverts when check unsuccessful", async () => { -// const { policyHarness, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// expect( -// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") -// }) - -// it("enforces post-check successfully", async () => { -// const { AdvancedERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) -// const targetAddress = await target.getAddress() - -// await policyHarness.setTarget(targetAddress) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) - -// const tx = await policyHarness -// .connect(target) -// .exposed__enforce(subjectAddress, [validEncodedNFTId], 2) -// const receipt = await tx.wait() -// const event = AdvancedERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect((await policyHarness.enforced(subjectAddress))[2]).to.be.equal(true) -// }) - -// it("reverts when post already enforced", async () => { -// const { policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployAdvancedPolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 0) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 1) -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) - -// await expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId], 2) -// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") -// }) -// }) -// }) -// }) - -// describe("Voting", () => { -// async function deployAdvancedVotingFixture() { -// const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = -// await ethers.getContractFactory("AdvancedERC721Checker") -// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = -// await ethers.getContractFactory("AdvancedERC721Policy") -// const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - -// const signupNft: NFT = await NFTFactory.deploy() -// const rewardNft: NFT = await NFTFactory.deploy() -// const signupIERC721Errors: IERC721Errors = await ethers.getContractAt( -// "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 advancedChecker.getAddress(), -// false, -// false, -// true -// ) - -// const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( -// await policy.getAddress() -// ) - -// // mint 0 for subject. -// await signupNft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validNFTId = 0 -// const invalidNFTId = 1 -// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) -// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) - -// return { -// signupIERC721Errors, -// rewardIERC721Errors, -// AdvancedVotingFactory, -// AdvancedERC721PolicyFactory, -// signupNft, -// rewardNft, -// advancedChecker, -// voting, -// policy, -// subject, -// deployer, -// notOwner, -// subjectAddress, -// notOwnerAddress, -// validNFTId, -// invalidNFTId, -// validEncodedNFTId, -// invalidEncodedNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { voting } = await loadFixture(deployAdvancedVotingFixture) - -// expect(voting).to.not.eq(undefined) -// }) -// }) - -// describe("register", () => { -// it("reverts when caller not target", async () => { -// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await notOwner.getAddress()) - -// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "TargetOnly" -// ) -// }) - -// it("reverts when evidence invalid", async () => { -// 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( -// signupIERC721Errors, -// "ERC721NonexistentToken" -// ) -// }) - -// it("reverts when check fails", async () => { -// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await voting.getAddress()) - -// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "UnsuccessfulCheck" -// ) -// }) - -// it("registers successfully", async () => { -// const { AdvancedVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = -// await loadFixture(deployAdvancedVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) - -// const tx = await voting.connect(subject).register(validNFTId) -// const receipt = await tx.wait() -// const event = AdvancedVotingFactory.interface.parseLog( -// receipt?.logs[1] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// voter: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(0n) -// expect(await voting.voteCounts(0)).to.be.equal(0) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) - -// it("reverts when already registered", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) - -// await voting.connect(subject).register(validNFTId) - -// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "AlreadyEnforced" -// ) -// }) -// }) - -// describe("vote", () => { -// it("reverts when not registered", async () => { -// const { voting, policy, subject } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await voting.getAddress()) - -// await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") -// }) - -// it("reverts when option invalid", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await voting.getAddress()) -// await voting.connect(subject).register(validNFTId) - -// await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") -// }) - -// it("votes successfully", async () => { -// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedVotingFixture) -// const option = 0 -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) -// await voting.connect(subject).register(validNFTId) - -// const tx = await voting.connect(subject).vote(option) -// const receipt = await tx.wait() -// const event = AdvancedVotingFactory.interface.parseLog( -// receipt?.logs[1] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// voter: string -// option: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect(event.args.option).to.eq(option) -// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) -// expect(await voting.voteCounts(0)).to.be.equal(1) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) - -// it("allows multiple votes", async () => { -// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedVotingFixture) -// const option = 0 -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) -// await voting.connect(subject).register(validNFTId) -// await voting.connect(subject).vote(option) - -// const tx = await voting.connect(subject).vote(option) -// const receipt = await tx.wait() -// const event = AdvancedVotingFactory.interface.parseLog( -// receipt?.logs[1] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// voter: string -// option: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect(event.args.option).to.eq(option) -// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(2n) -// expect(await voting.voteCounts(0)).to.be.equal(2) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) -// }) - -// describe("eligibility", () => { -// it("reverts when caller not target", async () => { -// const { voting, policy, subject, notOwner, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await notOwner.getAddress()) - -// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "TargetOnly" -// ) -// }) - -// 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 rewardNft.mint(subject) - -// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( -// policy, -// "UnsuccessfulCheck" -// ) -// }) - -// it("reverts when check fails", async () => { -// const { signupNft, rewardNft, deployer, voting, policy, notOwner, subject, validNFTId } = -// await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await voting.getAddress()) -// 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 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 } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await notOwner.getAddress()) - -// await expect(voting.connect(notOwner).eligible()).to.be.revertedWithCustomError(voting, "NotRegistered") -// }) - -// it("reverts when not voted", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployAdvancedVotingFixture) - -// await policy.setTarget(await voting.getAddress()) -// await voting.connect(subject).register(validNFTId) - -// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError(voting, "NotVoted") -// }) - -// it("verifies eligibility successfully", async () => { -// const { AdvancedVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = -// await loadFixture(deployAdvancedVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) -// await voting.connect(subject).register(validNFTId) -// await voting.connect(subject).vote(0) - -// 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 } -// ) as unknown as { -// args: { -// voter: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect((await policy.enforced(subjectAddress))[0]).to.be.equal(true) -// expect((await policy.enforced(subjectAddress))[1]).to.be.equal(1n) -// expect((await policy.enforced(subjectAddress))[2]).to.be.equal(true) -// expect(await voting.voteCounts(0)).to.be.equal(1) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) - -// 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).eligible() - -// await expect(voting.connect(subject).eligible()).to.be.revertedWithCustomError( -// voting, -// "AlreadyEligible" -// ) -// }) -// }) -// describe("end to end", () => { -// it("completes full voting lifecycle", async () => { -// const [deployer]: Signer[] = await ethers.getSigners() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const AdvancedERC721CheckerFactory: AdvancedERC721Checker__factory = -// await ethers.getContractFactory("AdvancedERC721Checker") -// const AdvancedERC721PolicyFactory: AdvancedERC721Policy__factory = -// await ethers.getContractFactory("AdvancedERC721Policy") -// const AdvancedVotingFactory: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") - -// const signupNft: NFT = await NFTFactory.deploy() -// const rewardNft: NFT = await NFTFactory.deploy() -// const baseChecker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await signupNft.getAddress() -// ]) -// const advancedChecker: AdvancedERC721Checker = await AdvancedERC721CheckerFactory.connect( -// deployer -// ).deploy( -// [await signupNft.getAddress(), await rewardNft.getAddress(), await baseChecker.getAddress()], -// 1, -// 0, -// 10 -// ) - -// const policy: AdvancedERC721Policy = await AdvancedERC721PolicyFactory.connect(deployer).deploy( -// await advancedChecker.getAddress(), -// false, -// false, -// true -// ) - -// const voting: AdvancedVoting = await AdvancedVotingFactory.connect(deployer).deploy( -// await policy.getAddress() -// ) - -// // set the target. -// const targetAddress = await voting.getAddress() -// await policy.setTarget(targetAddress) - -// for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { -// const voterAddress = await voter.getAddress() - -// // mint for voter. -// await signupNft.connect(deployer).mint(voterAddress) - -// // register. -// await voting.connect(voter).register(tokenId) - -// // vote. -// await voting.connect(voter).vote(tokenId % 2) - -// // reward. -// await voting.connect(voter).eligible() - -// expect((await policy.enforced(voterAddress))[0]).to.be.equal(true) -// expect((await policy.enforced(voterAddress))[1]).to.be.equal(1) -// expect((await policy.enforced(voterAddress))[2]).to.be.equal(true) -// } -// }) -// }) -// }) -// }) +import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" +import { ethers } from "hardhat" +import { expect } from "chai" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { + NFT__factory, + BaseERC721Checker__factory, + BaseERC721CheckerFactory__factory, + AdvancedERC721CheckerFactory__factory, + NFT, + BaseERC721Checker, + BaseERC721CheckerFactory, + AdvancedERC721CheckerFactory, + IERC721Errors, + AdvancedERC721Checker__factory, + AdvancedERC721Checker, + AdvancedERC721PolicyFactory__factory, + AdvancedERC721PolicyFactory, + AdvancedERC721Policy, + AdvancedERC721Policy__factory, + AdvancedVoting__factory, + AdvancedVoting +} from "../typechain-types" + +/* eslint-disable @typescript-eslint/no-shadow */ +describe("Advanced", () => { + describe("Checker", () => { + async function deployAdvancedCheckerFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + // mint 0 for subject. + await signupNft.connect(deployer).mint(subjectAddress) + + // encoded token ids. + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + + return { + signupNft, + rewardNft, + baseChecker, + advancedChecker, + deployer, + target, + subject, + subjectAddress, + notOwnerAddress, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { + const { advancedChecker } = await loadFixture(deployAdvancedCheckerFixture) + + expect(advancedChecker).to.not.eq(undefined) + expect(await advancedChecker.initialized()).to.be.eq(true) + }) + + it("should revert when already initialized", async () => { + const { advancedChecker, deployer } = await loadFixture(deployAdvancedCheckerFixture) + + await expect(advancedChecker.connect(deployer).initialize()).to.be.revertedWithCustomError( + advancedChecker, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { advancedChecker, signupNft, rewardNft, baseChecker } = + await loadFixture(deployAdvancedCheckerFixture) + + const appendedBytes = await advancedChecker.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "address", "uint256", "uint256", "uint256"], + [ + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ] + ) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) + }) + }) + + describe("check", () => { + describe("pre check", () => { + it("reverts when evidence invalid", async () => { + const { rewardNft, advancedChecker, target, subjectAddress, invalidEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + await expect( + advancedChecker.connect(target).check(subjectAddress, [invalidEncodedNFTId], 0) + ).to.be.revertedWithCustomError(rewardNft, "ERC721NonexistentToken") + }) + + it("returns false when not owner", async () => { + const { advancedChecker, target, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + expect( + await advancedChecker.connect(target).check(notOwnerAddress, [validEncodedNFTId], 0) + ).to.be.equal(false) + }) + + it("succeeds when valid", async () => { + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + expect( + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 0) + ).to.be.equal(true) + }) + }) + describe("main check", () => { + it("returns false when balance insufficient", async () => { + const { advancedChecker, target, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + expect( + await advancedChecker.connect(target).check(notOwnerAddress, [validEncodedNFTId], 1) + ).to.be.equal(false) + }) + + it("succeeds when balance sufficient", async () => { + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + expect( + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 1) + ).to.be.equal(true) + }) + }) + describe("post check", () => { + it("reverts when already rewarded", async () => { + const { rewardNft, advancedChecker, target, subjectAddress, invalidEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + await rewardNft.mint(subjectAddress) + + expect( + await advancedChecker.connect(target).check(subjectAddress, [invalidEncodedNFTId], 2) + ).to.be.equal(false) + }) + + it("succeeds when in valid range", async () => { + const { advancedChecker, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedCheckerFixture) + + expect( + await advancedChecker.connect(target).check(subjectAddress, [validEncodedNFTId], 2) + ).to.be.equal(true) + }) + }) + }) + }) + + describe("Policy", () => { + async function deployAdvancedPolicyFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await signupNft.getAddress() + ) + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() + + const advancedPolicyTx = await advancedPolicyFactory.deploy( + await advancedChecker.getAddress(), + false, + false, + true + ) + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicSkippedTx = await advancedPolicyFactory.deploy( + await advancedChecker.getAddress(), + true, + true, + false + ) + const advancedPolicySkippedTxReceipt = await advancedPolicSkippedTx.wait() + const advancedPolicySkippedCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicySkippedTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicySkipped: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicySkippedCloneDeployedEvent.args.clone, + deployer + ) + + // mint 0 for subject. + 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, + signupNft, + rewardNft, + baseChecker, + advancedChecker, + advancedPolicy, + advancedPolicySkipped, + deployer, + target, + notOwner, + subject, + subjectAddress, + notOwnerAddress, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { + const { advancedPolicy, advancedPolicySkipped } = await loadFixture(deployAdvancedPolicyFixture) + + expect(advancedPolicy).to.not.eq(undefined) + expect(await advancedPolicy.initialized()).to.be.eq(true) + expect(advancedPolicySkipped).to.not.eq(undefined) + expect(await advancedPolicySkipped.initialized()).to.be.eq(true) + }) + + it("should revert when already initialized", async () => { + const { advancedPolicy, advancedPolicySkipped, deployer } = + await loadFixture(deployAdvancedPolicyFixture) + + await expect(advancedPolicy.connect(deployer).initialize()).to.be.revertedWithCustomError( + advancedPolicy, + "AlreadyInitialized" + ) + await expect(advancedPolicySkipped.connect(deployer).initialize()).to.be.revertedWithCustomError( + advancedPolicySkipped, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { advancedPolicy, advancedPolicySkipped, advancedChecker, deployer } = + await loadFixture(deployAdvancedPolicyFixture) + + const appendedBytes = await advancedPolicy.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "bool", "bool", "bool"], + [await deployer.getAddress(), await advancedChecker.getAddress(), false, false, true] + ) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) + + const appendedBytesSkipped = await advancedPolicySkipped.getAppendedBytes.staticCall() + + const expectedBytesSkipped = AbiCoder.defaultAbiCoder() + .encode( + ["address", "address", "bool", "bool", "bool"], + [await deployer.getAddress(), await advancedChecker.getAddress(), true, true, false] + ) + .toLowerCase() + + expect(appendedBytesSkipped).to.equal(expectedBytesSkipped) + }) + }) + + describe("trait", () => { + it("returns correct value", async () => { + const { advancedPolicy, advancedPolicySkipped } = await loadFixture(deployAdvancedPolicyFixture) + + expect(await advancedPolicy.trait()).to.be.eq("AdvancedERC721") + expect(await advancedPolicySkipped.trait()).to.be.eq("AdvancedERC721") + }) + }) + + describe("setTarget", () => { + it("reverts when caller not owner", async () => { + const { advancedPolicy, notOwner, target } = await loadFixture(deployAdvancedPolicyFixture) + + await expect( + advancedPolicy.connect(notOwner).setTarget(await target.getAddress()) + ).to.be.revertedWithCustomError(advancedPolicy, "OwnableUnauthorizedAccount") + }) + + it("reverts when zero address", async () => { + const { advancedPolicy, deployer } = await loadFixture(deployAdvancedPolicyFixture) + + await expect(advancedPolicy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( + advancedPolicy, + "ZeroAddress" + ) + }) + + it("sets target correctly", async () => { + const { advancedPolicy, target } = await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + + const tx = await advancedPolicy.setTarget(targetAddress) + const receipt = await tx.wait() + const event = advancedPolicy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + target: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.target).to.eq(targetAddress) + }) + + it("reverts when already set", async () => { + const { advancedPolicy, target } = await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + + await advancedPolicy.setTarget(targetAddress) + + await expect(advancedPolicy.setTarget(targetAddress)).to.be.revertedWithCustomError( + advancedPolicy, + "TargetAlreadySet" + ) + }) + }) + + describe("enforce", () => { + describe("pre check", () => { + it("reverts when caller not target", async () => { + const { advancedPolicy, subject, target, subjectAddress } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + await expect( + advancedPolicy.connect(subject).enforce(subjectAddress, [ZeroHash], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") + }) + + it("reverts when evidence invalid", async () => { + const { iERC721Errors, advancedPolicy, target, subjectAddress, invalidEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + await expect( + advancedPolicy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 0) + ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + }) + + it("reverts when pre-check skipped", async () => { + const { advancedPolicySkipped, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicySkipped.setTarget(await target.getAddress()) + + await expect( + advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "CannotPreCheckWhenSkipped") + }) + + it("reverts when check unsuccessful", async () => { + const { advancedPolicy, target, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + expect( + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") + }) + + it("enforces pre-check successfully", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + + await advancedPolicy.setTarget(targetAddress) + + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + const receipt = await tx.wait() + const event = advancedPolicy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + subject: string + target: string + evidence: string + checkType: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.subject).to.eq(subjectAddress) + expect(event.args.target).to.eq(targetAddress) + expect(event.args.evidence[0]).to.eq(validEncodedNFTId) + expect(event.args.checkType).to.eq(0) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + }) + + it("reverts when pre already enforced", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + + await expect( + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + ).to.be.revertedWithCustomError(advancedPolicy, "AlreadyEnforced") + }) + }) + + describe("main check", () => { + it("reverts when pre-check missing", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + expect( + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicy, "PreCheckNotEnforced") + }) + + it("reverts when check unsuccessful", async () => { + const { advancedPolicy, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + + expect( + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") + }) + + it("enforces main-check successfully", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + const receipt = await tx.wait() + const event = advancedPolicy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + subject: string + target: string + evidence: string + checkType: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.subject).to.eq(subjectAddress) + expect(event.args.target).to.eq(targetAddress) + expect(event.args.evidence[0]).to.eq(validEncodedNFTId) + expect(event.args.checkType).to.eq(1) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1) + }) + + it("executes multiple mains when allowed", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + await advancedPolicy.setTarget(targetAddress) + + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + const receipt = await tx.wait() + const event = advancedPolicy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + subject: string + target: string + evidence: string + checkType: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.subject).to.eq(subjectAddress) + expect(event.args.target).to.eq(targetAddress) + expect(event.args.evidence[0]).to.eq(validEncodedNFTId) + expect(event.args.checkType).to.eq(1) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(2) + }) + + it("reverts when main check already enfored", async () => { + const { advancedPolicySkipped, target, notOwnerAddress, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicySkipped.setTarget(await target.getAddress()) + await advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + expect( + advancedPolicySkipped.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 1) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "MainCheckAlreadyEnforced") + }) + }) + + describe("post check", () => { + it("reverts when pre/main missing", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + + expect( + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "PreCheckNotEnforced") + + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + + expect( + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "MainCheckNotEnforced") + }) + + it("reverts when caller not target", async () => { + const { advancedPolicy, subject, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + await expect( + advancedPolicy.connect(subject).enforce(subjectAddress, [ZeroHash], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") + }) + + it("reverts when already rewarded", async () => { + const { + rewardNft, + advancedPolicy, + target, + subjectAddress, + validEncodedNFTId, + invalidEncodedNFTId + } = await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + await rewardNft.mint(subjectAddress) + + await expect( + advancedPolicy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") + }) + + it("reverts when post-check skipped", async () => { + const { advancedPolicySkipped, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicySkipped.setTarget(await target.getAddress()) + await advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + await expect( + advancedPolicySkipped.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicySkipped, "CannotPostCheckWhenSkipped") + }) + + it("reverts when check unsuccessful", async () => { + const { advancedPolicy, target, subjectAddress, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + expect( + advancedPolicy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") + }) + + it("enforces post-check successfully", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + const targetAddress = await target.getAddress() + + await advancedPolicy.setTarget(targetAddress) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + + const tx = await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + const receipt = await tx.wait() + const event = advancedPolicy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + subject: string + target: string + evidence: string + checkType: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.subject).to.eq(subjectAddress) + expect(event.args.target).to.eq(targetAddress) + expect(event.args.evidence[0]).to.eq(validEncodedNFTId) + expect(event.args.checkType).to.eq(2) + expect((await advancedPolicy.enforced(subjectAddress))[2]).to.be.equal(true) + }) + + it("reverts when post already enforced", async () => { + const { advancedPolicy, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedPolicyFixture) + + await advancedPolicy.setTarget(await target.getAddress()) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 0) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 1) + await advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + + await expect( + advancedPolicy.connect(target).enforce(subjectAddress, [validEncodedNFTId], 2) + ).to.be.revertedWithCustomError(advancedPolicy, "AlreadyEnforced") + }) + }) + }) + }) + + describe("Voting", () => { + async function deployAdvancedVotingFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + const AdvancedVoting: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt( + "IERC721Errors", + await signupNft.getAddress() + ) + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() + + const advancedPolicyTx = await advancedPolicyFactory.deploy( + await advancedChecker.getAddress(), + false, + false, + true + ) + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) + + const advancedVoting: AdvancedVoting = await AdvancedVoting.connect(deployer).deploy( + await advancedPolicy.getAddress() + ) + + // mint 0 for subject. + 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, + signupNft, + rewardNft, + baseChecker, + advancedChecker, + advancedPolicy, + advancedVoting, + deployer, + target, + notOwner, + subject, + subjectAddress, + notOwnerAddress, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("constructor", () => { + it("deploys correctly", async () => { + const { advancedVoting } = await loadFixture(deployAdvancedVotingFixture) + + expect(advancedVoting).to.not.eq(undefined) + }) + }) + + describe("register", () => { + it("reverts when caller not target", async () => { + const { advancedVoting, advancedPolicy, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await notOwner.getAddress()) + + await expect( + advancedVoting.connect(notOwner).register(validEncodedNFTId) + ).to.be.revertedWithCustomError(advancedPolicy, "TargetOnly") + }) + + it("reverts when evidence invalid", async () => { + const { iERC721Errors, advancedVoting, advancedPolicy, subject, invalidEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + + await expect( + advancedVoting.connect(subject).register(invalidEncodedNFTId) + ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + }) + + it("reverts when check fails", async () => { + const { advancedVoting, advancedPolicy, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + + await expect( + advancedVoting.connect(notOwner).register(validEncodedNFTId) + ).to.be.revertedWithCustomError(advancedPolicy, "UnsuccessfulCheck") + }) + + it("registers successfully", async () => { + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId, subjectAddress } = + await loadFixture(deployAdvancedVotingFixture) + const targetAddress = await advancedVoting.getAddress() + + await advancedPolicy.setTarget(targetAddress) + + const tx = await advancedVoting.connect(subject).register(validEncodedNFTId) + const receipt = await tx.wait() + const event = advancedVoting.interface.parseLog( + receipt?.logs[1] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(0n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(0) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) + }) + + it("reverts when already registered", async () => { + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + const targetAddress = await advancedVoting.getAddress() + + await advancedPolicy.setTarget(targetAddress) + + await advancedVoting.connect(subject).register(validEncodedNFTId) + + await expect(advancedVoting.connect(subject).register(validEncodedNFTId)).to.be.revertedWithCustomError( + advancedPolicy, + "AlreadyEnforced" + ) + }) + }) + + describe("vote", () => { + it("reverts when not registered", async () => { + const { advancedVoting, advancedPolicy, subject } = await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + + await expect(advancedVoting.connect(subject).vote(0)).to.be.revertedWithCustomError( + advancedVoting, + "NotRegistered" + ) + }) + + it("reverts when option invalid", async () => { + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + + await expect(advancedVoting.connect(subject).vote(3)).to.be.revertedWithCustomError( + advancedVoting, + "InvalidOption" + ) + }) + + it("votes successfully", async () => { + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + const option = 0 + const targetAddress = await advancedVoting.getAddress() + + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) + + const tx = await advancedVoting.connect(subject).vote(option) + const receipt = await tx.wait() + const event = advancedVoting.interface.parseLog( + receipt?.logs[1] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + option: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect(event.args.option).to.eq(option) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(1) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) + }) + + it("allows multiple votes", async () => { + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + const option = 0 + const targetAddress = await advancedVoting.getAddress() + + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(option) + + const tx = await advancedVoting.connect(subject).vote(option) + const receipt = await tx.wait() + const event = advancedVoting.interface.parseLog( + receipt?.logs[1] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + option: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect(event.args.option).to.eq(option) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(2n) + expect(await advancedVoting.voteCounts(0)).to.be.equal(2) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) + }) + }) + + describe("eligibility", () => { + it("reverts when caller not target", async () => { + const { advancedVoting, advancedPolicy, subject, notOwner, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await notOwner.getAddress()) + + await expect(advancedVoting.connect(subject).register(validEncodedNFTId)).to.be.revertedWithCustomError( + advancedPolicy, + "TargetOnly" + ) + }) + + it("reverts when already owns reward token", async () => { + const { rewardNft, advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + + await rewardNft.mint(subject) + + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedPolicy, + "UnsuccessfulCheck" + ) + }) + + it("reverts when check fails", async () => { + const { + signupNft, + rewardNft, + deployer, + advancedVoting, + advancedPolicy, + notOwner, + subject, + validEncodedNFTId + } = await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await signupNft.connect(deployer).mint(notOwner) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + await advancedVoting.connect(notOwner).register(1) + await advancedVoting.connect(notOwner).vote(0) + + await rewardNft.connect(deployer).mint(subject) + + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedPolicy, + "UnsuccessfulCheck" + ) + }) + + it("reverts when not registered", async () => { + const { advancedVoting, advancedPolicy, notOwner } = await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await notOwner.getAddress()) + + await expect(advancedVoting.connect(notOwner).eligible()).to.be.revertedWithCustomError( + advancedVoting, + "NotRegistered" + ) + }) + + it("reverts when not voted", async () => { + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedVoting, + "NotVoted" + ) + }) + + it("verifies eligibility successfully", async () => { + const { advancedVoting, advancedPolicy, subject, subjectAddress, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + const targetAddress = await advancedVoting.getAddress() + + await advancedPolicy.setTarget(targetAddress) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + + const tx = await advancedVoting.connect(subject).eligible() + const receipt = await tx.wait() + const event = advancedVoting.interface.parseLog( + receipt?.logs[1] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect((await advancedPolicy.enforced(subjectAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(subjectAddress))[1]).to.be.equal(1n) + expect((await advancedPolicy.enforced(subjectAddress))[2]).to.be.equal(true) + expect(await advancedVoting.voteCounts(0)).to.be.equal(1) + expect(await advancedVoting.voteCounts(1)).to.be.equal(0) + }) + + it("reverts when already eligible", async () => { + const { advancedVoting, advancedPolicy, subject, validEncodedNFTId } = + await loadFixture(deployAdvancedVotingFixture) + + await advancedPolicy.setTarget(await advancedVoting.getAddress()) + await advancedVoting.connect(subject).register(validEncodedNFTId) + await advancedVoting.connect(subject).vote(0) + await advancedVoting.connect(subject).eligible() + + await expect(advancedVoting.connect(subject).eligible()).to.be.revertedWithCustomError( + advancedVoting, + "AlreadyEligible" + ) + }) + }) + + describe("end to end", () => { + it("completes full voting lifecycle", async () => { + const [deployer]: Signer[] = await ethers.getSigners() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const AdvancedERC721CheckerFactory: AdvancedERC721CheckerFactory__factory = + await ethers.getContractFactory("AdvancedERC721CheckerFactory") + const AdvancedERC721PolicyFactory: AdvancedERC721PolicyFactory__factory = + await ethers.getContractFactory("AdvancedERC721PolicyFactory") + const AdvancedVoting: AdvancedVoting__factory = await ethers.getContractFactory("AdvancedVoting") + + const signupNft: NFT = await NFT.deploy() + const rewardNft: NFT = await NFT.deploy() + + const baseCheckerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + + const baseCheckerTx = await baseCheckerFactory.deploy(await signupNft.getAddress()) + const baseCheckerTxReceipt = await baseCheckerTx.wait() + const baseCheckerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + baseCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const baseChecker: BaseERC721Checker = BaseERC721Checker__factory.connect( + baseCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedCheckerFactory: AdvancedERC721CheckerFactory = + await AdvancedERC721CheckerFactory.connect(deployer).deploy() + + const advancedCheckerTx = await advancedCheckerFactory.deploy( + await signupNft.getAddress(), + await rewardNft.getAddress(), + await baseChecker.getAddress(), + 1, + 0, + 10 + ) + const advancedCheckerTxReceipt = await advancedCheckerTx.wait() + const advancedCheckerCloneDeployedEvent = AdvancedERC721CheckerFactory.interface.parseLog( + advancedCheckerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedChecker: AdvancedERC721Checker = AdvancedERC721Checker__factory.connect( + advancedCheckerCloneDeployedEvent.args.clone, + deployer + ) + + const advancedPolicyFactory: AdvancedERC721PolicyFactory = + await AdvancedERC721PolicyFactory.connect(deployer).deploy() + + const advancedPolicyTx = await advancedPolicyFactory.deploy( + await advancedChecker.getAddress(), + false, + false, + true + ) + const advancedPolicyTxReceipt = await advancedPolicyTx.wait() + const advancedPolicyCloneDeployedEvent = AdvancedERC721PolicyFactory.interface.parseLog( + advancedPolicyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const advancedPolicy: AdvancedERC721Policy = AdvancedERC721Policy__factory.connect( + advancedPolicyCloneDeployedEvent.args.clone, + deployer + ) + + const advancedVoting: AdvancedVoting = await AdvancedVoting.connect(deployer).deploy( + await advancedPolicy.getAddress() + ) + + // set the target. + const targetAddress = await advancedVoting.getAddress() + await advancedPolicy.setTarget(targetAddress) + + for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { + const voterAddress = await voter.getAddress() + + // mint for voter. + await signupNft.connect(deployer).mint(voterAddress) + + // register. + await advancedVoting.connect(voter).register(tokenId) + + // vote. + await advancedVoting.connect(voter).vote(tokenId % 2) + + // reward. + await advancedVoting.connect(voter).eligible() + + expect((await advancedPolicy.enforced(voterAddress))[0]).to.be.equal(true) + expect((await advancedPolicy.enforced(voterAddress))[1]).to.be.equal(1) + expect((await advancedPolicy.enforced(voterAddress))[2]).to.be.equal(true) + } + }) + }) + }) +}) diff --git a/packages/contracts/test/Base.test.ts b/packages/contracts/test/Base.test.ts index 5241158..a1e7237 100644 --- a/packages/contracts/test/Base.test.ts +++ b/packages/contracts/test/Base.test.ts @@ -1,625 +1,655 @@ -// @todo refactoring - -// describe("Base", () => { -// describe("Checker", () => { -// async function deployBaseCheckerFixture() { -// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const BaseERC721CheckerHarnessFactory: BaseERC721CheckerHarness__factory = -// await ethers.getContractFactory("BaseERC721CheckerHarness") - -// const nft: NFT = await NFTFactory.deploy() -// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await nft.getAddress() -// ]) -// const checkerHarness: BaseERC721CheckerHarness = await BaseERC721CheckerHarnessFactory.connect( -// deployer -// ).deploy([await nft.getAddress()]) - -// // mint 0 for subject. -// await nft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) -// const invalidNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - -// return { -// nft, -// checker, -// checkerHarness, -// target, -// subjectAddress, -// notOwnerAddress, -// validNFTId, -// invalidNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { checker } = await loadFixture(deployBaseCheckerFixture) - -// expect(checker).to.not.eq(undefined) -// }) -// }) - -// 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 } = -// await loadFixture(deployBaseCheckerFixture) - -// await expect( -// checker.connect(target).check(subjectAddress, [invalidNFTId]) -// ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") -// }) - -// it("returns false when subject not owner", async () => { -// const { checker, target, notOwnerAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) - -// expect(await checker.connect(target).check(notOwnerAddress, [validNFTId])).to.be.equal(false) -// }) - -// it("succeeds when valid", async () => { -// const { checker, target, subjectAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture) - -// expect(await checker.connect(target).check(subjectAddress, [validNFTId])).to.be.equal(true) -// }) -// }) - -// describe("internal check", () => { -// it("reverts when evidence is invalid", async () => { -// const { nft, checkerHarness, target, subjectAddress, invalidNFTId } = -// await loadFixture(deployBaseCheckerFixture) - -// await expect( -// checkerHarness.connect(target).exposed__check(subjectAddress, [invalidNFTId]) -// ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") -// }) - -// it("returns false when subject not owner", async () => { -// const { checkerHarness, target, notOwnerAddress, validNFTId } = -// await loadFixture(deployBaseCheckerFixture) - -// expect(await checkerHarness.connect(target).exposed__check(notOwnerAddress, [validNFTId])).to.be.equal( -// false -// ) -// }) - -// it("succeeds when valid", async () => { -// const { checkerHarness, target, subjectAddress, validNFTId } = -// await loadFixture(deployBaseCheckerFixture) - -// expect(await checkerHarness.connect(target).exposed__check(subjectAddress, [validNFTId])).to.be.equal( -// true -// ) -// }) -// }) -// }) - -// describe("Policy", () => { -// async function deployBasePolicyFixture() { -// const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const BaseERC721PolicyFactory: BaseERC721Policy__factory = -// await ethers.getContractFactory("BaseERC721Policy") -// const BaseERC721PolicyHarnessFactory: BaseERC721PolicyHarness__factory = -// await ethers.getContractFactory("BaseERC721PolicyHarness") - -// const nft: NFT = await NFTFactory.deploy() -// const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - -// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await nft.getAddress() -// ]) -// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( -// await checker.getAddress() -// ) -// const policyHarness: BaseERC721PolicyHarness = await BaseERC721PolicyHarnessFactory.connect( -// deployer -// ).deploy(await checker.getAddress()) - -// // mint 0 for subject. -// await nft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) -// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) - -// return { -// iERC721Errors, -// BaseERC721PolicyFactory, -// nft, -// policyHarness, -// policy, -// subject, -// deployer, -// target, -// notOwner, -// subjectAddress, -// notOwnerAddress, -// validEncodedNFTId, -// invalidEncodedNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { policy } = await loadFixture(deployBasePolicyFixture) - -// expect(policy).to.not.eq(undefined) -// }) -// }) - -// describe("trait", () => { -// it("returns correct value", async () => { -// const { policy } = await loadFixture(deployBasePolicyFixture) - -// expect(await policy.trait()).to.be.eq("BaseERC721") -// }) -// }) - -// describe("setTarget", () => { -// it("reverts when caller not owner", async () => { -// const { policy, notOwner, target } = await loadFixture(deployBasePolicyFixture) - -// await expect( -// policy.connect(notOwner).setTarget(await target.getAddress()) -// ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") -// }) - -// it("reverts when zero address", async () => { -// const { policy, deployer } = await loadFixture(deployBasePolicyFixture) - -// await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( -// policy, -// "ZeroAddress" -// ) -// }) - -// it("sets target correctly", async () => { -// const { policy, target, BaseERC721PolicyFactory } = await loadFixture(deployBasePolicyFixture) -// const targetAddress = await target.getAddress() - -// const tx = await policy.setTarget(targetAddress) -// const receipt = await tx.wait() -// const event = BaseERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// target: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.target).to.eq(targetAddress) -// expect(await policy.getTarget()).to.eq(targetAddress) -// }) - -// it("reverts when already set", async () => { -// const { policy, target } = await loadFixture(deployBasePolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(targetAddress) - -// await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") -// }) -// }) - -// describe("enforce", () => { -// it("reverts when caller not target", async () => { -// const { policy, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// await expect(policy.connect(subject).enforce(subjectAddress, [ZeroHash])).to.be.revertedWithCustomError( -// policy, -// "TargetOnly" -// ) -// }) - -// it("reverts when evidence invalid", async () => { -// const { iERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// await expect( -// policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId]) -// ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") -// }) - -// it("reverts when check fails", async () => { -// const { policy, target, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// expect( -// policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId]) -// ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") -// }) - -// it("enforces successfully", async () => { -// const { BaseERC721PolicyFactory, policy, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) -// const targetAddress = await target.getAddress() - -// await policy.setTarget(await target.getAddress()) - -// const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) -// const receipt = await tx.wait() -// const event = BaseERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(await policy.enforced(subjectAddress)).to.be.equal(true) -// }) - -// it("reverts when already enforced", async () => { -// const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) - -// await policy.setTarget(await target.getAddress()) - -// await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) - -// await expect( -// policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) -// ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") -// }) -// }) - -// describe("internal enforce", () => { -// it("reverts when caller not target", async () => { -// const { policyHarness, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await expect( -// policyHarness.connect(subject).exposed__enforce(subjectAddress, [ZeroHash]) -// ).to.be.revertedWithCustomError(policyHarness, "TargetOnly") -// }) - -// it("reverts when evidence invalid", async () => { -// const { iERC721Errors, policyHarness, target, subjectAddress, invalidEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await expect( -// policyHarness.connect(target).exposed__enforce(subjectAddress, [invalidEncodedNFTId]) -// ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") -// }) - -// it("reverts when check fails", async () => { -// const { policyHarness, target, notOwnerAddress, validEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// expect( -// policyHarness.connect(target).exposed__enforce(notOwnerAddress, [validEncodedNFTId]) -// ).to.be.revertedWithCustomError(policyHarness, "UnsuccessfulCheck") -// }) - -// it("enforces successfully", async () => { -// const { BaseERC721PolicyFactory, policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) -// const targetAddress = await target.getAddress() - -// await policyHarness.setTarget(await target.getAddress()) - -// const tx = await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) -// const receipt = await tx.wait() -// const event = BaseERC721PolicyFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// subject: string -// target: string -// evidence: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.subject).to.eq(subjectAddress) -// expect(event.args.target).to.eq(targetAddress) -// expect(event.args.evidence[0]).to.eq(validEncodedNFTId) -// expect(await policyHarness.enforced(subjectAddress)).to.be.equal(true) -// }) - -// it("reverts when already enforced", async () => { -// const { policyHarness, target, subjectAddress, validEncodedNFTId } = -// await loadFixture(deployBasePolicyFixture) - -// await policyHarness.setTarget(await target.getAddress()) - -// await policyHarness.connect(target).exposed__enforce(subjectAddress, [validEncodedNFTId]) - -// await expect( -// policyHarness.connect(target).enforce(subjectAddress, [validEncodedNFTId]) -// ).to.be.revertedWithCustomError(policyHarness, "AlreadyEnforced") -// }) -// }) -// }) - -// describe("Voting", () => { -// async function deployBaseVotingFixture() { -// const [deployer, subject, notOwner]: Signer[] = await ethers.getSigners() -// const subjectAddress: string = await subject.getAddress() -// const notOwnerAddress: string = await notOwner.getAddress() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const BaseERC721PolicyFactory: BaseERC721Policy__factory = -// await ethers.getContractFactory("BaseERC721Policy") -// const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") - -// const nft: NFT = await NFTFactory.deploy() -// const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) - -// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await nft.getAddress() -// ]) -// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( -// await checker.getAddress() -// ) -// const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) - -// // mint 0 for subject. -// await nft.connect(deployer).mint(subjectAddress) - -// // encoded token ids. -// const validNFTId = 0 -// const invalidNFTId = 1 -// const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) -// const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) - -// return { -// iERC721Errors, -// BaseVotingFactory, -// nft, -// voting, -// policy, -// subject, -// deployer, -// notOwner, -// subjectAddress, -// notOwnerAddress, -// validNFTId, -// invalidNFTId, -// validEncodedNFTId, -// invalidEncodedNFTId -// } -// } - -// describe("constructor", () => { -// it("deploys correctly", async () => { -// const { voting } = await loadFixture(deployBaseVotingFixture) - -// expect(voting).to.not.eq(undefined) -// }) -// }) - -// describe("register", () => { -// it("reverts when caller not target", async () => { -// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) - -// await policy.setTarget(await notOwner.getAddress()) - -// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "TargetOnly" -// ) -// }) - -// it("reverts when evidence invalid", async () => { -// const { iERC721Errors, voting, policy, subject, invalidNFTId } = -// await loadFixture(deployBaseVotingFixture) - -// await policy.setTarget(await voting.getAddress()) - -// await expect(voting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( -// iERC721Errors, -// "ERC721NonexistentToken" -// ) -// }) - -// it("reverts when check fails", async () => { -// const { voting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) - -// await policy.setTarget(await voting.getAddress()) - -// await expect(voting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "UnsuccessfulCheck" -// ) -// }) - -// it("registers successfully", async () => { -// const { BaseVotingFactory, voting, policy, subject, validNFTId, subjectAddress } = -// await loadFixture(deployBaseVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) - -// const tx = await voting.connect(subject).register(validNFTId) -// const receipt = await tx.wait() -// const event = BaseVotingFactory.interface.parseLog( -// receipt?.logs[1] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// voter: string -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect(await policy.enforced(subjectAddress)).to.be.equal(true) -// expect(await voting.hasVoted(subjectAddress)).to.be.equal(false) -// expect(await voting.voteCounts(0)).to.be.equal(0) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) - -// it("reverts when already registered", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) - -// await voting.connect(subject).register(validNFTId) - -// await expect(voting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( -// policy, -// "AlreadyEnforced" -// ) -// }) -// }) - -// describe("vote", () => { -// it("reverts when not registered", async () => { -// const { voting, policy, subject } = await loadFixture(deployBaseVotingFixture) - -// await policy.setTarget(await voting.getAddress()) - -// await expect(voting.connect(subject).vote(0)).to.be.revertedWithCustomError(voting, "NotRegistered") -// }) - -// it("reverts when option invalid", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) - -// await policy.setTarget(await voting.getAddress()) -// await voting.connect(subject).register(validNFTId) - -// await expect(voting.connect(subject).vote(3)).to.be.revertedWithCustomError(voting, "InvalidOption") -// }) - -// it("votes successfully", async () => { -// const { BaseVotingFactory, voting, policy, subject, subjectAddress, validNFTId } = -// await loadFixture(deployBaseVotingFixture) -// const option = 0 - -// await policy.setTarget(await voting.getAddress()) -// await voting.connect(subject).register(validNFTId) - -// const tx = await voting.connect(subject).vote(option) -// const receipt = await tx.wait() -// const event = BaseVotingFactory.interface.parseLog( -// receipt?.logs[0] as unknown as { topics: string[]; data: string } -// ) as unknown as { -// args: { -// voter: string -// option: number -// } -// } - -// expect(receipt?.status).to.eq(1) -// expect(event.args.voter).to.eq(subjectAddress) -// expect(event.args.option).to.eq(option) -// expect(await voting.hasVoted(subjectAddress)).to.be.equal(true) -// expect(await voting.voteCounts(0)).to.be.equal(1) -// expect(await voting.voteCounts(1)).to.be.equal(0) -// }) - -// it("reverts when already voted", async () => { -// const { voting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) -// const targetAddress = await voting.getAddress() - -// await policy.setTarget(targetAddress) -// await voting.connect(subject).register(validNFTId) - -// await voting.connect(subject).vote(0) - -// await expect(voting.connect(subject).vote(1)).to.be.revertedWithCustomError(voting, "AlreadyVoted") -// }) -// }) - -// describe("end to end", () => { -// it("completes full voting flow", async () => { -// const [deployer]: Signer[] = await ethers.getSigners() - -// const NFTFactory: NFT__factory = await ethers.getContractFactory("NFT") -// const BaseERC721CheckerFactory: BaseERC721Checker__factory = -// await ethers.getContractFactory("BaseERC721Checker") -// const BaseERC721PolicyFactory: BaseERC721Policy__factory = -// await ethers.getContractFactory("BaseERC721Policy") -// const BaseVotingFactory: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") - -// const nft: NFT = await NFTFactory.deploy() - -// const checker: BaseERC721Checker = await BaseERC721CheckerFactory.connect(deployer).deploy([ -// await nft.getAddress() -// ]) -// const policy: BaseERC721Policy = await BaseERC721PolicyFactory.connect(deployer).deploy( -// await checker.getAddress() -// ) -// const voting: BaseVoting = await BaseVotingFactory.connect(deployer).deploy(await policy.getAddress()) - -// // set the target. -// await policy.setTarget(await voting.getAddress()) - -// for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { -// const voterAddress = await voter.getAddress() - -// // mint for voter. -// await nft.connect(deployer).mint(voterAddress) - -// // register. -// await voting.connect(voter).register(tokenId) - -// // vote. -// await voting.connect(voter).vote(tokenId % 2) - -// expect(await voting.hasVoted(voter)).to.be.equal(true) -// } -// }) -// }) -// }) -// }) +import { AbiCoder, Signer, ZeroAddress, ZeroHash } from "ethers" +import { ethers } from "hardhat" +import { expect } from "chai" +import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers" +import { + NFT__factory, + BaseERC721Checker__factory, + BaseERC721Policy__factory, + BaseERC721CheckerFactory__factory, + BaseERC721PolicyFactory__factory, + NFT, + BaseERC721Checker, + BaseERC721Policy, + BaseERC721CheckerFactory, + BaseERC721PolicyFactory, + IERC721Errors, + BaseVoting__factory, + BaseVoting +} from "../typechain-types" + +/* eslint-disable @typescript-eslint/no-shadow */ +describe("Base", () => { + describe("Checker", () => { + async function deployBaseCheckerFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + + const nft: NFT = await NFT.deploy() + const factory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + + const tx = await factory.deploy(await nft.getAddress()) + const receipt = await tx.wait() + const event = BaseERC721CheckerFactory.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect(event.args.clone, deployer) + + // mint 0 for subject. + await nft.connect(deployer).mint(subjectAddress) + + // encoded token ids. + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + + return { + nft, + checker, + factory, + deployer, + target, + subjectAddress, + notOwnerAddress, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { + const { checker } = await loadFixture(deployBaseCheckerFixture) + + expect(checker).to.not.eq(undefined) + expect(await checker.initialized()).to.be.eq(true) + }) + + it("should revert when already initialized", async () => { + const { checker, deployer } = await loadFixture(deployBaseCheckerFixture) + + await expect(checker.connect(deployer).initialize()).to.be.revertedWithCustomError( + checker, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { checker, nft } = await loadFixture(deployBaseCheckerFixture) + + const appendedBytes = await checker.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode(["address"], [await nft.getAddress()]) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) + }) + }) + + describe("check", () => { + it("reverts when evidence is invalid", async () => { + const { nft, checker, target, subjectAddress, invalidEncodedNFTId } = + await loadFixture(deployBaseCheckerFixture) + + await expect( + checker.connect(target).check(subjectAddress, [invalidEncodedNFTId]) + ).to.be.revertedWithCustomError(nft, "ERC721NonexistentToken") + }) + + it("returns false when subject not owner", async () => { + const { checker, target, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployBaseCheckerFixture) + + expect(await checker.connect(target).check(notOwnerAddress, [validEncodedNFTId])).to.be.equal(false) + }) + + it("succeeds when valid", async () => { + const { checker, target, subjectAddress, validEncodedNFTId } = + await loadFixture(deployBaseCheckerFixture) + + expect(await checker.connect(target).check(subjectAddress, [validEncodedNFTId])).to.be.equal(true) + }) + }) + }) + + describe("Policy", () => { + async function deployBasePolicyFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") + + const nft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) + + const checkerFactory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() + + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer + ) + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, + deployer + ) as BaseERC721Policy + + // mint 0 for subject. + await nft.connect(deployer).mint(subjectAddress) + + // encoded token ids. + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [0]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [1]) + + return { + iERC721Errors, + nft, + checker, + policy, + subject, + deployer, + target, + notOwner, + subjectAddress, + notOwnerAddress, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("initialize", () => { + it("should deploy and initialize correctly", async () => { + const { policy } = await loadFixture(deployBasePolicyFixture) + + expect(policy).to.not.eq(undefined) + expect(await policy.initialized()).to.be.eq(true) + }) + + it("should revert when already initialized", async () => { + const { policy, deployer } = await loadFixture(deployBasePolicyFixture) + + await expect(policy.connect(deployer).initialize()).to.be.revertedWithCustomError( + policy, + "AlreadyInitialized" + ) + }) + }) + + describe("getAppendedBytes", () => { + it("should append bytes correctly", async () => { + const { policy, checker, deployer } = await loadFixture(deployBasePolicyFixture) + + const appendedBytes = await policy.getAppendedBytes.staticCall() + + const expectedBytes = AbiCoder.defaultAbiCoder() + .encode(["address", "address"], [await deployer.getAddress(), await checker.getAddress()]) + .toLowerCase() + + expect(appendedBytes).to.equal(expectedBytes) + }) + }) + + describe("trait", () => { + it("returns correct value", async () => { + const { policy } = await loadFixture(deployBasePolicyFixture) + + expect(await policy.trait()).to.be.eq("BaseERC721") + }) + }) + + describe("setTarget", () => { + it("reverts when caller not owner", async () => { + const { policy, notOwner, target } = await loadFixture(deployBasePolicyFixture) + + await expect( + policy.connect(notOwner).setTarget(await target.getAddress()) + ).to.be.revertedWithCustomError(policy, "OwnableUnauthorizedAccount") + }) + + it("reverts when zero address", async () => { + const { policy, deployer } = await loadFixture(deployBasePolicyFixture) + + await expect(policy.connect(deployer).setTarget(ZeroAddress)).to.be.revertedWithCustomError( + policy, + "ZeroAddress" + ) + }) + + it("sets target correctly", async () => { + const { policy, target } = await loadFixture(deployBasePolicyFixture) + const targetAddress = await target.getAddress() + const tx = await policy.setTarget(targetAddress) + const receipt = await tx.wait() + const event = policy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + target: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.target).to.eq(targetAddress) + }) + + it("reverts when already set", async () => { + const { policy, target } = await loadFixture(deployBasePolicyFixture) + const targetAddress = await target.getAddress() + + await policy.setTarget(targetAddress) + + await expect(policy.setTarget(targetAddress)).to.be.revertedWithCustomError(policy, "TargetAlreadySet") + }) + }) + + describe("enforce", () => { + it("reverts when caller not target", async () => { + const { policy, subject, target, subjectAddress } = await loadFixture(deployBasePolicyFixture) + + await policy.setTarget(await target.getAddress()) + + await expect(policy.connect(subject).enforce(subjectAddress, [ZeroHash])).to.be.revertedWithCustomError( + policy, + "TargetOnly" + ) + }) + + it("reverts when evidence invalid", async () => { + const { iERC721Errors, policy, target, subjectAddress, invalidEncodedNFTId } = + await loadFixture(deployBasePolicyFixture) + + await policy.setTarget(await target.getAddress()) + + await expect( + policy.connect(target).enforce(subjectAddress, [invalidEncodedNFTId]) + ).to.be.revertedWithCustomError(iERC721Errors, "ERC721NonexistentToken") + }) + + it("reverts when check fails", async () => { + const { policy, target, notOwnerAddress, validEncodedNFTId } = + await loadFixture(deployBasePolicyFixture) + + await policy.setTarget(await target.getAddress()) + + expect( + policy.connect(target).enforce(notOwnerAddress, [validEncodedNFTId]) + ).to.be.revertedWithCustomError(policy, "UnsuccessfulCheck") + }) + + it("enforces successfully", async () => { + const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) + const targetAddress = await target.getAddress() + + await policy.setTarget(await target.getAddress()) + + const tx = await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) + const receipt = await tx.wait() + const event = policy.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + subject: string + target: string + evidence: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.subject).to.eq(subjectAddress) + expect(event.args.target).to.eq(targetAddress) + expect(event.args.evidence[0]).to.eq(validEncodedNFTId) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) + }) + + it("reverts when already enforced", async () => { + const { policy, target, subjectAddress, validEncodedNFTId } = await loadFixture(deployBasePolicyFixture) + + await policy.setTarget(await target.getAddress()) + + await policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) + + await expect( + policy.connect(target).enforce(subjectAddress, [validEncodedNFTId]) + ).to.be.revertedWithCustomError(policy, "AlreadyEnforced") + }) + }) + }) + + describe("Voting", () => { + async function deployBaseVotingFixture() { + const [deployer, subject, target, notOwner]: Signer[] = await ethers.getSigners() + const subjectAddress: string = await subject.getAddress() + const notOwnerAddress: string = await notOwner.getAddress() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") + const BaseVoting: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + + const nft: NFT = await NFT.deploy() + const iERC721Errors: IERC721Errors = await ethers.getContractAt("IERC721Errors", await nft.getAddress()) + + const checkerFactory: BaseERC721CheckerFactory = await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() + + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer + ) + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, + deployer + ) as BaseERC721Policy + + const baseVoting: BaseVoting = await BaseVoting.connect(deployer).deploy(await policy.getAddress()) + + // mint 0 for subject. + await nft.connect(deployer).mint(subjectAddress) + + // encoded token ids. + const validNFTId = 0 + const invalidNFTId = 1 + const validEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [validNFTId]) + const invalidEncodedNFTId = AbiCoder.defaultAbiCoder().encode(["uint256"], [invalidNFTId]) + + return { + iERC721Errors, + nft, + checker, + policy, + baseVoting, + subject, + deployer, + target, + notOwner, + subjectAddress, + notOwnerAddress, + validNFTId, + invalidNFTId, + validEncodedNFTId, + invalidEncodedNFTId + } + } + + describe("constructor", () => { + it("deploys correctly", async () => { + const { baseVoting, subject } = await loadFixture(deployBaseVotingFixture) + + expect(baseVoting).to.not.eq(undefined) + expect(await baseVoting.hasVoted(subject)).to.be.eq(false) + expect(await baseVoting.voteCounts(0)).to.be.eq(0) + }) + }) + + describe("register", () => { + it("reverts when caller not target", async () => { + const { baseVoting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + + await policy.setTarget(await notOwner.getAddress()) + + await expect(baseVoting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( + policy, + "TargetOnly" + ) + }) + + it("reverts when evidence invalid", async () => { + const { iERC721Errors, baseVoting, policy, subject, invalidNFTId } = + await loadFixture(deployBaseVotingFixture) + + await policy.setTarget(await baseVoting.getAddress()) + + await expect(baseVoting.connect(subject).register(invalidNFTId)).to.be.revertedWithCustomError( + iERC721Errors, + "ERC721NonexistentToken" + ) + }) + + it("reverts when check fails", async () => { + const { baseVoting, policy, notOwner, validNFTId } = await loadFixture(deployBaseVotingFixture) + + await policy.setTarget(await baseVoting.getAddress()) + + await expect(baseVoting.connect(notOwner).register(validNFTId)).to.be.revertedWithCustomError( + policy, + "UnsuccessfulCheck" + ) + }) + + it("registers successfully", async () => { + const { baseVoting, policy, subject, validNFTId, subjectAddress } = + await loadFixture(deployBaseVotingFixture) + const targetAddress = await baseVoting.getAddress() + + await policy.setTarget(targetAddress) + + const tx = await baseVoting.connect(subject).register(validNFTId) + const receipt = await tx.wait() + const event = baseVoting.interface.parseLog( + receipt?.logs[1] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect(await policy.enforced(subjectAddress)).to.be.equal(true) + expect(await baseVoting.hasVoted(subjectAddress)).to.be.equal(false) + expect(await baseVoting.voteCounts(0)).to.be.equal(0) + expect(await baseVoting.voteCounts(1)).to.be.equal(0) + }) + + it("reverts when already registered", async () => { + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + const targetAddress = await baseVoting.getAddress() + + await policy.setTarget(targetAddress) + + await baseVoting.connect(subject).register(validNFTId) + + await expect(baseVoting.connect(subject).register(validNFTId)).to.be.revertedWithCustomError( + policy, + "AlreadyEnforced" + ) + }) + }) + + describe("vote", () => { + it("reverts when not registered", async () => { + const { baseVoting, policy, subject } = await loadFixture(deployBaseVotingFixture) + + await policy.setTarget(await baseVoting.getAddress()) + + await expect(baseVoting.connect(subject).vote(0)).to.be.revertedWithCustomError( + baseVoting, + "NotRegistered" + ) + }) + + it("reverts when option invalid", async () => { + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + + await policy.setTarget(await baseVoting.getAddress()) + await baseVoting.connect(subject).register(validNFTId) + + await expect(baseVoting.connect(subject).vote(3)).to.be.revertedWithCustomError( + baseVoting, + "InvalidOption" + ) + }) + + it("votes successfully", async () => { + const { baseVoting, policy, subject, subjectAddress, validNFTId } = + await loadFixture(deployBaseVotingFixture) + const option = 0 + + await policy.setTarget(await baseVoting.getAddress()) + await baseVoting.connect(subject).register(validNFTId) + + const tx = await baseVoting.connect(subject).vote(option) + const receipt = await tx.wait() + const event = baseVoting.interface.parseLog( + receipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + voter: string + option: number + } + } + + expect(receipt?.status).to.eq(1) + expect(event.args.voter).to.eq(subjectAddress) + expect(event.args.option).to.eq(option) + expect(await baseVoting.hasVoted(subjectAddress)).to.be.equal(true) + expect(await baseVoting.voteCounts(0)).to.be.equal(1) + expect(await baseVoting.voteCounts(1)).to.be.equal(0) + }) + + it("reverts when already voted", async () => { + const { baseVoting, policy, subject, validNFTId } = await loadFixture(deployBaseVotingFixture) + const targetAddress = await baseVoting.getAddress() + + await policy.setTarget(targetAddress) + await baseVoting.connect(subject).register(validNFTId) + + await baseVoting.connect(subject).vote(0) + + await expect(baseVoting.connect(subject).vote(1)).to.be.revertedWithCustomError( + baseVoting, + "AlreadyVoted" + ) + }) + }) + + describe("end to end", () => { + it("completes full voting flow", async () => { + const [deployer]: Signer[] = await ethers.getSigners() + + const NFT: NFT__factory = await ethers.getContractFactory("NFT") + const BaseERC721CheckerFactory: BaseERC721CheckerFactory__factory = + await ethers.getContractFactory("BaseERC721CheckerFactory") + const BaseERC721PolicyFactory: BaseERC721PolicyFactory__factory = + await ethers.getContractFactory("BaseERC721PolicyFactory") + const BaseVoting: BaseVoting__factory = await ethers.getContractFactory("BaseVoting") + + const nft: NFT = await NFT.deploy() + + const checkerFactory: BaseERC721CheckerFactory = + await BaseERC721CheckerFactory.connect(deployer).deploy() + const policyFactory: BaseERC721PolicyFactory = await BaseERC721PolicyFactory.connect(deployer).deploy() + + const checkerTx = await checkerFactory.deploy(await nft.getAddress()) + const checkerTxReceipt = await checkerTx.wait() + const checkerCloneDeployedEvent = BaseERC721CheckerFactory.interface.parseLog( + checkerTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const checker: BaseERC721Checker = BaseERC721Checker__factory.connect( + checkerCloneDeployedEvent.args.clone, + deployer + ) + + const policyTx = await policyFactory.deploy(await checker.getAddress()) + const policyTxReceipt = await policyTx.wait() + const policyCloneDeployedEvent = BaseERC721PolicyFactory.interface.parseLog( + policyTxReceipt?.logs[0] as unknown as { topics: string[]; data: string } + ) as unknown as { + args: { + clone: string + } + } + + const policy = BaseERC721Policy__factory.connect( + policyCloneDeployedEvent.args.clone, + deployer + ) as BaseERC721Policy + + const baseVoting: BaseVoting = await BaseVoting.connect(deployer).deploy(await policy.getAddress()) + + // set the target. + await policy.setTarget(await baseVoting.getAddress()) + + for (const [tokenId, voter] of (await ethers.getSigners()).entries()) { + const voterAddress = await voter.getAddress() + + // mint for voter. + await nft.connect(deployer).mint(voterAddress) + + // register. + await baseVoting.connect(voter).register(tokenId) + + // vote. + await baseVoting.connect(voter).vote(tokenId % 2) + + expect(await baseVoting.hasVoted(voter)).to.be.equal(true) + } + }) + }) + }) +})