بنقرة واحدة
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