Skip to content

Commit

Permalink
test(contracts): add example implementation and tests for BaseChecker…
Browse files Browse the repository at this point in the history
… contract
  • Loading branch information
0xjei committed Nov 26, 2024
1 parent 53ce02c commit 9a6d052
Show file tree
Hide file tree
Showing 23 changed files with 278 additions and 3 deletions.
3 changes: 2 additions & 1 deletion packages/contracts/.gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
node_modules
.env
/cache

# Hardhat files
/cache
/cache-hh
/artifacts

# Forge files
Expand Down
4 changes: 3 additions & 1 deletion packages/contracts/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"license": "MIT",
"files": [
"*.sol",
"**/*.sol"
"!test/*",
"README.md",
"LICENSE"
],
"keywords": [
"blockchain",
Expand Down
File renamed without changes.
21 changes: 21 additions & 0 deletions packages/contracts/contracts/src/test/BaseERC721Checker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

contract BaseERC721Checker is BaseChecker {
IERC721 public immutable NFT;

constructor(IERC721 _nft) {
NFT = IERC721(_nft);
}

function _check(address subject, bytes memory evidence) internal view override returns (bool) {
// Decode the tokenId from the evidence.
uint256 tokenId = abi.decode(evidence, (uint256));

// Return true if the subject is the owner of the tokenId, false otherwise.
return NFT.ownerOf(tokenId) == subject;
}
}
17 changes: 17 additions & 0 deletions packages/contracts/contracts/src/test/BaseERC721Policy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

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

contract BaseERC721Policy is BasePolicy {
BaseERC721Checker public immutable CHECKER;

constructor(BaseERC721Checker _checker) BasePolicy(_checker) {
CHECKER = BaseERC721Checker(_checker);
}

function trait() external pure returns (string memory) {
return "BaseERC721";
}
}
44 changes: 44 additions & 0 deletions packages/contracts/contracts/src/test/BaseVoting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {BaseERC721Policy} from "./BaseERC721Policy.sol";

contract BaseVoting {
event Registered(address voter);
event Voted(address voter, uint8 option);

error NotRegistered();
error AlreadyVoted();
error InvalidOption();

BaseERC721Policy public immutable POLICY;

// Mapping to track if an address has voted
mapping(address => bool) public hasVoted;
// Mapping to count votes for each option
mapping(uint8 => uint256) public voteCounts;

constructor(BaseERC721Policy _policy) {
POLICY = _policy;
}

// Function to register a voter using a the policy enforcement.
function register(uint256 tokenId) external {
bytes memory evidence = abi.encode(tokenId);
POLICY.enforce(msg.sender, evidence);

emit Registered(msg.sender);
}

// Function to cast a vote for a given option.
function vote(uint8 option) external {
if (!POLICY.enforced(address(this), msg.sender)) revert NotRegistered();
if (hasVoted[msg.sender]) revert AlreadyVoted();
if (option >= 2) revert InvalidOption();

hasVoted[msg.sender] = true;
voteCounts[option]++;

emit Voted(msg.sender, option);
}
}
15 changes: 15 additions & 0 deletions packages/contracts/contracts/src/test/NFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

contract NFT is ERC721 {
uint256 private _tokenIdCounter;

constructor() ERC721("NFT", "NFT") {}

function mint(address to) external {
_safeMint(to, _tokenIdCounter);
_tokenIdCounter++;
}
}
87 changes: 87 additions & 0 deletions packages/contracts/contracts/test/Base.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {Test} from "forge-std/src/Test.sol";
import {NFT} from "../src/test/NFT.sol";
import {BaseERC721Checker} from "../src/test/BaseERC721Checker.sol";
import {BaseERC721CheckerHarness} from "./wrappers/BaseERC721CheckerHarness.sol";
import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";

contract BaseChecker is Test {
NFT internal nft;
BaseERC721Checker internal checker;
BaseERC721CheckerHarness internal checkerHarness;

address public deployer = vm.addr(0x1);
address public target = vm.addr(0x2);
address public subject = vm.addr(0x3);
address public notOwner = vm.addr(0x4);

function setUp() public virtual {
vm.startPrank(deployer);

nft = new NFT();
checker = new BaseERC721Checker(nft);
checkerHarness = new BaseERC721CheckerHarness(nft);

vm.stopPrank();
}

function test_check_internal_RevertWhen_ERC721NonexistentToken() public {
vm.startPrank(target);

vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0)));
checkerHarness.exposed__check(subject, abi.encode(0));

vm.stopPrank();
}

function test_check_internal_return_False() public {
vm.startPrank(target);

nft.mint(subject);

assert(!checkerHarness.exposed__check(notOwner, abi.encode(0)));

vm.stopPrank();
}

function test_check_Internal() public {
vm.startPrank(target);

nft.mint(subject);

assert(checkerHarness.exposed__check(subject, abi.encode(0)));

vm.stopPrank();
}

function test_check_RevertWhen_ERC721NonexistentToken() public {
vm.startPrank(target);

vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, uint256(0)));
checker.check(subject, abi.encode(0));

vm.stopPrank();
}

function test_check_return_False() public {
vm.startPrank(target);

nft.mint(subject);

assert(!checker.check(notOwner, abi.encode(0)));

vm.stopPrank();
}

function test_check() public {
vm.startPrank(target);

nft.mint(subject);

assert(checker.check(subject, abi.encode(0)));

vm.stopPrank();
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;

import {BaseERC721Checker} from "../../src/test/BaseERC721Checker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

// This contract is a harness for testing the BaseERC721Checker contract.
// Deploy this contract and call its methods to test the internal methods of BaseERC721Checker.
contract BaseERC721CheckerHarness is BaseERC721Checker {
constructor(IERC721 _nft) BaseERC721Checker(_nft) {}

/// @notice Exposes the internal `_check` method for testing purposes.
/// @param subject The address to be checked.
/// @param evidence The data associated with the check.
function exposed__check(address subject, bytes calldata evidence) public view returns (bool) {
return _check(subject, evidence);
}
}
2 changes: 1 addition & 1 deletion packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const config: HardhatUserConfig = {
paths: {
sources: "./contracts/src",
tests: "./test",
cache: "./cache",
cache: "./cache-hh",
artifacts: "./artifacts"
},
networks: {
Expand Down
File renamed without changes.
Empty file removed packages/contracts/test/.gitkeep
Empty file.
70 changes: 70 additions & 0 deletions packages/contracts/test/Base.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect } from "chai"
import { ethers } from "hardhat"
import { AbiCoder, Signer } from "ethers"
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers"
import { BaseERC721Checker, BaseERC721Checker__factory, NFT, NFT__factory } from "../typechain-types"

describe("BaseChecker", () => {
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 nft: NFT = await NFTFactory.deploy()
const checker: BaseERC721Checker = await BaseERC721CheckerFactory.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,
target,
subjectAddress,
notOwnerAddress,
validNFTId,
invalidNFTId
}
}

describe("constructor()", () => {
it("Should deploy the checker contract correctly", async () => {
const { checker } = await loadFixture(deployBaseCheckerFixture)

expect(checker).to.not.eq(undefined)
})
})

describe("check()", () => {
it("should revert the check when the evidence is not meaningful", async () => {
const { nft, checker, target, subjectAddress, invalidNFTId } = await loadFixture(deployBaseCheckerFixture)

await expect(checker.connect(target).check(subjectAddress, invalidNFTId)).to.be.revertedWithCustomError(
nft,
"ERC721NonexistentToken"
)
})

it("should return false when the subject is not the owner of the evidenced token", async () => {
const { checker, target, notOwnerAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture)

expect(await checker.connect(target).check(notOwnerAddress, validNFTId)).to.be.equal(false)
})

it("should check", async () => {
const { checker, target, subjectAddress, validNFTId } = await loadFixture(deployBaseCheckerFixture)

expect(await checker.connect(target).check(subjectAddress, validNFTId)).to.be.equal(true)
})
})
})

0 comments on commit 9a6d052

Please sign in to comment.