Sui Move Testing
Test the invariant and threat surface, not only the happy-path function name.
Target repository instructions, accepted design records, pinned toolchain behavior, and published compatibility commitments take precedence over this standard's examples.
Build the risk matrix
- Inventory every changed behavior, invariant, public or entry path, asset,
authority, lifecycle edge, event, dependency, and upgrade seam. The inventory
is complete when every production change has a test owner.
- Mark each risk layer below applicable or inapplicable for every inventoried
behavior, with a reason for each inapplicable layer.
- Read every matching specialized reference, then map each applicable risk to
a direct test, property, structural check, integration check, or release gate.
- Run the release gates below and use the final completion gate to report the
result.
Cover every risk layer
- Pure math: tables, boundaries, monotonicity, inverse dust bounds, and properties.
- Lifecycle: every legal edge and rejection from every illegal source state.
- Authorization: allowed and forbidden actions, rotation, revocation, last-authority destruction or handoff, pause, and version precedence.
- Accounting: reserves, fees, allocations, vesting, burns, and conservation after operations and sequences.
- Adversarial: direct public calls, PTB composition, replay, third-party signature submission, expiry, wrong domain, wrong type or object, slippage, stale or selectable oracle data, callback-like handoffs, randomness selection, duplicate settlement, and hostile assets.
- Events: every payload field and full reducer replay against final state.
- Upgrades: cover compatibility, permitted generic-constraint relaxation,
initializer non-rerun, old-version safety or operational rejection,
capability binding, policy narrowing, new-type migration, and dependency
relinking. Reject incompatible changes to existing public function
signatures, struct or enum layouts, and abilities.
- Deployment and dependencies: dry run, finality, normalized artifacts, exact pins, and live external ABI checks.
Load matching references
- Fixtures: For
test_scenario, multi-transaction, or resource-carrying
setup, read Stateful fixtures completely.
- Object custody: For bounded collections, dynamic child storage,
identity-bearing objects, wrapping, or key-only returned objects, read
Object custody and storage tests
completely.
- Adversarial integrations: For time, oracles, signatures, hostile assets,
randomness, callback-like composition, or external integrations, read
Adversarial integration tests
completely.
- Event replay: For events, payloads, indexers, or replay, read
Event replay tests completely.
- Upgrades and dependencies: For publication, upgrades, migration,
activation, dependency relinking, deployed ABI, or fingerprints, read
Upgrade and dependency tests
completely.
Read every matching reference for a task. Keep the loaded set to test variants
whose risk appears in the target; a complete review still loads every matching
reference.
Write direct tests
- Start each test module with
#[test_only] module.
- Use the production section style with title-cased headings.
- Name each test as a behavioral statement.
- Keep pure math and error-registry tests fixture-free. Pin every package error
number and stable meaning in the registry test. For module-local
#[error(code = N)] constants, pin the explicit code, raising module, and
failed-condition message through the repository's supported test or source
gate.
- Give builders deterministic safe defaults and receiver-style
with_* modifiers so a test states only its relevant delta.
- Use fixed seeds for random tests and print enough input to reproduce failure.
Pin exact failures
Never use a bare expected failure:
#[test]
#[expected_failure(
abort_code = protocol::errors::EInvalidBps,
location = protocol::bps,
)]
fun constructor_rejects_the_first_value_above_the_limit() {
bps::new(10_001);
}
- Assert the exact intended abort and raising module.
- Reject new bare module-local
u64 error constants in simple packages; verify
that local errors retain their explicit #[error(code = N)] identity and
descriptive vector<u8> message.
- Add pairwise invalid-input tests to pin guard precedence.
- Assert framework errors when the framework is the intended enforcement layer.
- Ensure cleanup cannot make a negative test pass through an unrelated abort.
- When the harness exposes failed transaction effects, snapshot relevant state, assets, and events and prove the aborted path leaves them unchanged.
Test arithmetic and economics
- Include zero, one, maximum supported values, and just below/at/above every threshold.
- When a domain struct composes a sealed value struct, test the reusable
invariant at the value struct's construction and mutation boundary. Test each
domain constructor's additional relationships and at least one composed path;
do not repeat the same boundary matrix for every enclosing type unless that
type can bypass or alter the reusable invariant.
- Stress intermediate multiplication even when the final result fits.
- Test the declared rounding beneficiary with one-unit remainders.
- Assert the operation-specific conservation equation after each operation and long sequence.
- Add properties for monotonicity, bounded dust, inverse behavior, and allocation sums.
- Keep shared vectors explicit about units and regenerate them from one formula source; do not hand-edit expected values.
- When replacing custom logic with a pinned framework function or macro, retain boundary, rounding, and exact-abort tests; similarity of names does not prove semantic equivalence.
Run release gates
Use the exact pinned toolchain. A typical Sui Move gate is:
sui move build
sui move lint --warnings-are-errors
sui move test --lint
Also run repository formatting, source-boundary, dependency-pin, external-ABI,
coverage, and git diff --check gates when present. If the pinned coverage tool
misrepresents a branch, use named branch tests and document the limitation
instead of inventing a false metric.
Testing is complete only when every applicable risk in the matrix has passing
evidence, exact failure behavior is pinned, all available gates pass, and every
unavailable or unreliable check is reported.
1---2name: sui-move-testing3description: Testing for Sui Move invariants, exact aborts, guard precedence, stateful fixtures, arithmetic properties, event replay, adversarial integrations, upgrades, dependencies, and release gates. Use when adding, changing, fixing, or reviewing Move behavior or tests.4---56# Sui Move Testing78Test the invariant and threat surface, not only the happy-path function name.910Target repository instructions, accepted design records, pinned toolchain behavior, and published compatibility commitments take precedence over this standard's examples.1112## Build the risk matrix13141. Inventory every changed behavior, invariant, public or entry path, asset,15 authority, lifecycle edge, event, dependency, and upgrade seam. The inventory16 is complete when every production change has a test owner.172. Mark each risk layer below applicable or inapplicable for every inventoried18 behavior, with a reason for each inapplicable layer.193. Read every matching specialized reference, then map each applicable risk to20 a direct test, property, structural check, integration check, or release gate.214. Run the release gates below and use the final completion gate to report the22 result.2324## Cover every risk layer2526- Pure math: tables, boundaries, monotonicity, inverse dust bounds, and properties.27- Lifecycle: every legal edge and rejection from every illegal source state.28- Authorization: allowed and forbidden actions, rotation, revocation, last-authority destruction or handoff, pause, and version precedence.29- Accounting: reserves, fees, allocations, vesting, burns, and conservation after operations and sequences.30- Adversarial: direct public calls, PTB composition, replay, third-party signature submission, expiry, wrong domain, wrong type or object, slippage, stale or selectable oracle data, callback-like handoffs, randomness selection, duplicate settlement, and hostile assets.31- Events: every payload field and full reducer replay against final state.32- Upgrades: cover compatibility, permitted generic-constraint relaxation,33 initializer non-rerun, old-version safety or operational rejection,34 capability binding, policy narrowing, new-type migration, and dependency35 relinking. Reject incompatible changes to existing public function36 signatures, struct or enum layouts, and abilities.37- Deployment and dependencies: dry run, finality, normalized artifacts, exact pins, and live external ABI checks.3839## Load matching references4041- **Fixtures:** For `test_scenario`, multi-transaction, or resource-carrying42 setup, read [Stateful fixtures](references/stateful-fixtures.md) completely.43- **Object custody:** For bounded collections, dynamic child storage,44 identity-bearing objects, wrapping, or key-only returned objects, read45 [Object custody and storage tests](references/object-custody-tests.md)46 completely.47- **Adversarial integrations:** For time, oracles, signatures, hostile assets,48 randomness, callback-like composition, or external integrations, read49 [Adversarial integration tests](references/adversarial-integrations.md)50 completely.51- **Event replay:** For events, payloads, indexers, or replay, read52 [Event replay tests](references/event-replay-tests.md) completely.53- **Upgrades and dependencies:** For publication, upgrades, migration,54 activation, dependency relinking, deployed ABI, or fingerprints, read55 [Upgrade and dependency tests](references/upgrade-dependency-tests.md)56 completely.5758Read every matching reference for a task. Keep the loaded set to test variants59whose risk appears in the target; a complete review still loads every matching60reference.6162## Write direct tests6364- Start each test module with `#[test_only] module`.65- Use the production section style with title-cased headings.66- Name each test as a behavioral statement.67- Keep pure math and error-registry tests fixture-free. Pin every package error68 number and stable meaning in the registry test. For module-local69 `#[error(code = N)]` constants, pin the explicit code, raising module, and70 failed-condition message through the repository's supported test or source71 gate.72- Give builders deterministic safe defaults and receiver-style `with_*` modifiers so a test states only its relevant delta.73- Use fixed seeds for random tests and print enough input to reproduce failure.7475## Pin exact failures7677Never use a bare expected failure:7879```move80#[test]81#[expected_failure(82 abort_code = protocol::errors::EInvalidBps,83 location = protocol::bps,84)]85fun constructor_rejects_the_first_value_above_the_limit() {86 bps::new(10_001);87}88```8990- Assert the exact intended abort and raising module.91- Reject new bare module-local `u64` error constants in simple packages; verify92 that local errors retain their explicit `#[error(code = N)]` identity and93 descriptive `vector<u8>` message.94- Add pairwise invalid-input tests to pin guard precedence.95- Assert framework errors when the framework is the intended enforcement layer.96- Ensure cleanup cannot make a negative test pass through an unrelated abort.97- When the harness exposes failed transaction effects, snapshot relevant state, assets, and events and prove the aborted path leaves them unchanged.9899## Test arithmetic and economics100101- Include zero, one, maximum supported values, and just below/at/above every threshold.102- When a domain struct composes a sealed value struct, test the reusable103 invariant at the value struct's construction and mutation boundary. Test each104 domain constructor's additional relationships and at least one composed path;105 do not repeat the same boundary matrix for every enclosing type unless that106 type can bypass or alter the reusable invariant.107- Stress intermediate multiplication even when the final result fits.108- Test the declared rounding beneficiary with one-unit remainders.109- Assert the operation-specific conservation equation after each operation and long sequence.110- Add properties for monotonicity, bounded dust, inverse behavior, and allocation sums.111- Keep shared vectors explicit about units and regenerate them from one formula source; do not hand-edit expected values.112- When replacing custom logic with a pinned framework function or macro, retain boundary, rounding, and exact-abort tests; similarity of names does not prove semantic equivalence.113114## Run release gates115116Use the exact pinned toolchain. A typical Sui Move gate is:117118```bash119sui move build120sui move lint --warnings-are-errors121sui move test --lint122```123124Also run repository formatting, source-boundary, dependency-pin, external-ABI,125coverage, and `git diff --check` gates when present. If the pinned coverage tool126misrepresents a branch, use named branch tests and document the limitation127instead of inventing a false metric.128129Testing is complete only when every applicable risk in the matrix has passing130evidence, exact failure behavior is pinned, all available gates pass, and every131unavailable or unreliable check is reported.