一键导入
foundry-testing
Foundry testing patterns for Solidity contracts. Triggers: test, testing, forge test, coverage, solidity test
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Foundry testing patterns for Solidity contracts. Triggers: test, testing, forge test, coverage, solidity test
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
Deploy smart contracts to Polkadot Asset Hub. Triggers: deploy, deployment, paseo, mainnet
Deploy frontend to Bulletin Chain + DotNS. Triggers: deploy frontend, bulletin, dotns, .dot domain, decentralized hosting
dot.li universal resolver - the fourth host. Triggers: dot.li, smoldot, helia, link sharing, client-side resolution
基于 SOC 职业分类
| name | foundry-testing |
| description | Foundry testing patterns for Solidity contracts. Triggers: test, testing, forge test, coverage, solidity test |
THIS REPO DOES NOT USE FOUNDRY. The two Solidity contracts (P2PMarket.sol and ZKPassportRegistry.sol) are tested with Hardhat (
hardhat test, Mocha/Chai/ethers v6 via@nomicfoundation/hardhat-toolbox), with coverage viasolidity-coverage. Tests are TypeScript, not Solidity, and live in packages/contracts/test/ — P2PMarket.test.ts and ZKPassportRegistry.test.ts. There is noforge,foundry.toml, or*.t.sol. For the actual project test workflow see the/testingcommand (.claude/commands/testing.md). The Foundry/forgepatterns below are kept only as generic external reference — they are not how this project tests.
Note: this project's contracts are non-upgradeable (no proxy, no UUPS, no initializer). Disregard the upgradeable/proxy guidance below for this repo.
| Rule | Enforcement |
|---|---|
| Inherit from Test base | REQUIRED |
| Use vm.prank for caller | REQUIRED |
| Test revert conditions | REQUIRED |
| Name tests descriptively | test_Action or test_RevertWhen_Condition |
This repo: there is no proxy, no
initialize, and noMockERC20— escrow uses the chain native token (msg.value/.call{value:}), not an ERC-20. The real setup is a HardhatbeforeEachthat deploysP2PMarketdirectly and grabs signers fromethers.getSigners():// packages/contracts/test/P2PMarket.test.ts (Mocha/Chai/ethers v6) beforeEach(async function () { [owner, agent1, agent2, provider, buyer] = await ethers.getSigners(); const P2PMarketFactory = await ethers.getContractFactory('P2PMarket'); market = await P2PMarketFactory.deploy(); });The Solidity
setUp()fixture below is generic Foundry reference only.
// test/Base.t.sol
abstract contract BaseTest is Test {
MyContract public myContract;
MockToken public token;
address public admin = makeAddr("admin");
address public user1 = makeAddr("user1");
address public user2 = makeAddr("user2");
function setUp() public virtual {
// Deploy with proxies (if upgradeable)
MyContract impl = new MyContract();
ERC1967Proxy proxy = new ERC1967Proxy(
address(impl),
abi.encodeCall(MyContract.initialize, (admin))
);
myContract = MyContract(address(proxy));
// Deploy mock token
token = new MockToken();
// Fund test accounts
vm.deal(user1, 100 ether);
token.mint(user1, 1000e18);
}
}
| Pattern | Example |
|---|---|
| Happy path | test_CreateOrder |
| Revert condition | test_RevertWhen_InsufficientBalance |
| Edge case | test_CreateOrder_WithZeroAmount |
| Fuzz test | testFuzz_Transfer(uint256 amount) |
function test_CreateOrder() public {
vm.prank(user1);
uint256 orderId = myContract.createOrder(100);
assertEq(myContract.ownerOf(orderId), user1);
assertTrue(myContract.isActive(orderId));
}
function test_RevertWhen_NotOwner() public {
vm.prank(user1);
uint256 orderId = myContract.createOrder(100);
vm.prank(user2); // Not the owner
vm.expectRevert("Not owner");
myContract.cancelOrder(orderId);
}
function test_AdminFunction() public {
vm.prank(admin);
myContract.pause();
assertTrue(myContract.paused());
}
function test_MultipleActions() public {
vm.startPrank(user1);
uint256 id = myContract.createOrder(100);
myContract.updateOrder(id, 200);
vm.stopPrank();
}
function test_EmitsEvent() public {
vm.expectEmit(true, true, false, true);
emit OrderCreated(1, user1, 100);
vm.prank(user1);
myContract.createOrder(100);
}
function test_DepositNativeToken() public {
vm.prank(user1);
uint256 depositId = myContract.deposit{value: 1 ether}();
assertEq(address(myContract).balance, 1 ether);
}
This repo has no mock token. Escrow is the native token, and neither contract inherits OpenZeppelin (it is an unused devDependency). The generic
ERC20mock below is external reference only — do not assume aMockERC20exists inpackages/contracts/contracts/.
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// Generic Foundry reference only — NOT present in this repo.
contract MockToken is ERC20 {
constructor() ERC20("Mock", "MOCK") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
function testFuzz_Transfer(uint256 amount) public {
vm.assume(amount > 0 && amount <= 1000e18);
token.mint(user1, amount);
vm.prank(user1);
token.transfer(user2, amount);
assertEq(token.balanceOf(user2), amount);
}
Generic Foundry reference only — this repo does not fork-test. Its Hardhat networks are
hardhat(local, chainId 31337) andpaseo(AH Next, chainId 420420417,https://eth-rpc-paseo-next.polkadot.io).
function test_WithPaseoFork() public {
vm.createSelectFork("paseo");
// Test against real Paseo state
MyContract live = MyContract(DEPLOYED_ADDRESS);
assertTrue(live.isActive());
}
This repo's real trade flow uses native value, not ERC-20 approve/fund, and has no admin/owner. Value is sent with
lockTrade{value:}(stateLOCKED), then released by per-msg.senderconfirmations:confirmTrade(both parties →COMPLETED),confirmCashReceived(agent →RELEASED),confirmPickup(provider →COMPLETED),requestCancel(mutual →CANCELLED), orrefundTrade(anyone, after the 24hCONFIRMATION_TIMEOUTwhileLOCKED→REFUNDED). There is noDEFAULTEDstate and no stake-slashing. The 5 states areLOCKED,RELEASED,COMPLETED,REFUNDED,CANCELLED. The Solidity example below is generic Foundry reference only.
function test_FullWorkflow() public {
// 1. Setup
vm.prank(user1);
uint256 orderId = myContract.createOrder(100);
// 2. Fund order
vm.prank(user1);
token.approve(address(myContract), 100);
vm.prank(user1);
myContract.fundOrder(orderId, 100);
// 3. Execute
vm.prank(admin);
myContract.executeOrder(orderId);
// 4. Verify final state
assertEq(myContract.status(orderId), Status.Completed);
}
This repo uses Hardhat, not
forge. Frompackages/contracts/:pnpm test # hardhat test (Mocha/Chai/ethers v6) pnpm coverage # solidity-coverage # or from repo root: pnpm contracts:test # filter a single suite: pnpm exec hardhat test --grep "registerAgent"The
forgecommands below are generic external reference only.
# All tests
forge test
# Verbose output
forge test -vvv
# Specific file
forge test --match-path test/MyContract.t.sol
# Specific test
forge test --match-test test_CreateOrder
# Gas report
forge test --gas-report
# Coverage
forge coverage
The principles hold regardless of framework; the "How" column gives both the generic Foundry idiom and the this-repo Hardhat/ethers equivalent.
| Pattern | Status | Reason | How (this repo) |
|---|---|---|---|
| Test without assertions | FORBIDDEN | Tests must verify state | use chai expect/assert |
| Skip revert tests | FORBIDDEN | Security critical | await expect(...).to.be.revertedWithCustomError(...) |
| Hardcode addresses | FORBIDDEN | Deterministic accounts | use ethers.getSigners() |
| Forget the caller | FORBIDDEN | Wrong msg.sender | market.connect(signer).fn(...) (not vm.prank) |
The last contract is deployed directly (no proxy), so "test via proxy" does not apply here. Custom errors are used throughout, so prefer
revertedWithCustomErrorover string-match reverts.