원클릭으로
testing
Instructions for writing and organizing Foundry tests in the RuleEngine project
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Instructions for writing and organizing Foundry tests in the RuleEngine project
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | testing |
| description | Instructions for writing and organizing Foundry tests in the RuleEngine project |
This file provides project-specific test conventions, organization, and patterns for the RuleEngine codebase. For generic Foundry test cheatcodes, assertions, and deployment scripts, see the foundry skill.
test/
├── HelperContract.sol # Base helper for RuleEngine (RBAC) tests
├── HelperContractOwnable.sol # Base helper for RuleEngineOwnable tests
├── utils/
│ ├── CMTATDeployment.sol # CMTAT deployment helper
│ └── CMTATDeploymentV3.sol # CMTAT v3 deployment helper
├── RuleEngine/ # Tests for RuleEngine (RBAC variant)
│ ├── AccessControl/
│ ├── RulesManagementModuleTest/
│ ├── ruleEngineValidation/
│ ├── RuleEngineDeployment.t.sol
│ ├── RuleEngineCoverage.t.sol
│ ├── ERC3643Compliance.t.sol
│ └── IRuleInterfaceId.t.sol
├── RuleEngineOwnable/ # Tests for RuleEngineOwnable (ERC-173 variant)
│ ├── AccessControl/
│ ├── RulesManagementModuleTest/
│ ├── RuleEngineOwnableDeployment.t.sol
│ ├── RuleEngineOwnableCoverage.t.sol
│ └── ERC3643Compliance.t.sol
└── RuleWhitelist/ # Tests for the whitelist mock rule
├── AccessControl/
├── RuleWhitelist.t.sol
└── CMTATIntegration.t.sol
RuleEngine go in test/RuleEngine/RuleEngineOwnable go in test/RuleEngineOwnable/*Base.sol abstract contracts with virtual hooks.t.sol suffix; base/helper files use .solRuleEngineBase, add tests in both test/RuleEngine/ and test/RuleEngineOwnable/.import "./HelperContract.sol";
contract MyTest is Test, HelperContract {
// ...
}
.selector values)DEFAULT_ADMIN_ADDRESS = address(1)ruleEngineMock (RuleEngine), ruleWhitelist, ruleConditionalTransferLightresUint256, resUint8, resBool, resString, resAddrimport "../HelperContractOwnable.sol";
contract MyOwnableTest is Test, HelperContractOwnable {
// ...
}
HelperContract but for the Ownable variantOWNER_ADDRESS = address(1)ruleEngineMock (RuleEngineOwnable)NEW_OWNER_ADDRESS = address(8) for ownership transfer tests| Name | Address | Purpose |
|---|---|---|
DEFAULT_ADMIN_ADDRESS / OWNER_ADDRESS | address(1) | Admin/owner for RuleEngine / RuleEngineOwnable |
WHITELIST_OPERATOR_ADDRESS | address(2) | Whitelist rule operator |
RULE_ENGINE_OPERATOR_ADDRESS | address(3) | RuleEngine operator |
ATTACKER | address(4) | Unauthorized caller |
ADDRESS1 | address(5) | Test user 1 |
ADDRESS2 | address(6) | Test user 2 |
ADDRESS3 | address(7) | Test user 3 |
NEW_OWNER_ADDRESS | address(8) | Ownership transfer target (Ownable only) |
CONDITIONAL_TRANSFER_OPERATOR_ADDRESS | address(9) | Conditional transfer operator |
Test function names use camelCase with test prefix:
testCan<Action>() — positive test (should succeed)
testCannot<Action>() — negative test (expects revert)
testCannotAddEOAAsRule() — specific error case
testSupportsRuleEngineInterface() — ERC-165 interface check
Examples from the codebase:
testCanSetRules() — setting rules succeedstestCannotSetRuleIfARuleIsAlreadyPresent() — duplicate rule revertstestCannotSetEmptyRulesT1WithEmptyTab() — edge case with variant identifiertestMsgDataReturnsCalldata() — coverage test for internal functionAlways use specific error selectors, never bare vm.expectRevert():
// Good — specific error selector
vm.expectRevert(RuleEngine_RuleInvalidInterface.selector);
ruleEngineMock.addRule(IRule(address(0x999)));
// Bad — matches any revert
vm.expectRevert();
ruleEngineMock.addRule(IRule(address(0x999)));
Error selectors are available because test helpers inherit the invariant storage contracts (RuleEngineInvariantStorage, RulesManagementModuleInvariantStorage, etc.).
For functionality shared across RuleEngine and RuleEngineOwnable, use abstract base contracts with virtual hooks:
// Base contract defines shared tests + virtual hook
abstract contract CMTATIntegrationBase is Test, HelperContract {
function _deployCMTAT() internal virtual;
function setUp() public {
_deployCMTAT();
// shared setup: deploy RuleEngine, bind token, add rules, etc.
}
function testCanTransferWithWhitelist() public {
// shared test logic
}
}
// Concrete test implements the hook
contract CMTATIntegration is CMTATIntegrationBase {
function _deployCMTAT() internal override {
// deploy specific CMTAT version
}
}
// Another concrete test for a different CMTAT version
contract CMTATIntegrationV3 is CMTATIntegrationBase {
function _deployCMTAT() internal override {
// deploy CMTAT v3
}
}
This pattern is used for:
CMTATIntegrationBase — CMTAT integration tests across CMTAT versionsRuleEngineOperationRevertBase — revert scenario tests//SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../HelperContract.sol";
contract MyFeatureTest is Test, HelperContract {
RuleEngine ruleEngineMock;
RuleWhitelist ruleWhitelist;
function setUp() public {
ruleWhitelist = new RuleWhitelist(DEFAULT_ADMIN_ADDRESS, ZERO_ADDRESS);
ruleEngineMock = new RuleEngine(
DEFAULT_ADMIN_ADDRESS,
ZERO_ADDRESS, // forwarder
ZERO_ADDRESS // token (bound later)
);
}
function testCanDoSomething() public {
vm.prank(DEFAULT_ADMIN_ADDRESS);
ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
resUint256 = ruleEngineMock.rulesCount();
assertEq(resUint256, 1);
}
function testCannotDoSomethingUnauthorized() public {
vm.prank(ATTACKER);
vm.expectRevert(); // AccessControl revert
ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
}
}
//SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../HelperContractOwnable.sol";
contract MyFeatureOwnableTest is Test, HelperContractOwnable {
RuleEngineOwnable ruleEngineMock;
RuleWhitelist ruleWhitelist;
function setUp() public {
ruleWhitelist = new RuleWhitelist(OWNER_ADDRESS, ZERO_ADDRESS);
ruleEngineMock = new RuleEngineOwnable(
OWNER_ADDRESS,
ZERO_ADDRESS, // forwarder
ZERO_ADDRESS // token
);
}
function testCanDoSomething() public {
vm.prank(OWNER_ADDRESS);
ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
resUint256 = ruleEngineMock.rulesCount();
assertEq(resUint256, 1);
}
function testCannotDoSomethingUnauthorized() public {
vm.prank(ATTACKER);
vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", ATTACKER));
ruleEngineMock.addRule(IRule(address(ruleWhitelist)));
}
}
Coverage tests live in RuleEngineCoverage.t.sol / RuleEngineOwnableCoverage.t.sol and target hard-to-reach code paths:
supportsInterface() for all supported interface IDs_msgData() via exposed mock contracts (RuleEngineExposed, RuleEngineOwnableExposed)supportsInterfaceExposed mock contracts are in src/mocks/RuleEngineExposed.sol and expose internal functions for testing.
forge test # All tests
forge test --match-contract RuleEngineCoverage # Specific test contract
forge test --match-test testCanSetRules # Specific test function
forge test -vvv # Verbose (shows revert reasons)
forge test --gas-report # Gas usage report
forge coverage # Code coverage