Skip to content

Commit fb36b6e

Browse files
authored
Add multi-agent simulation specifications and TypeScript steps (#30)
feat: add and stabilize multi-agent simulation support - Add multi-agent simulation specs and TS steps - Improve simulation validation rules and key handling (sender / secondary / fee payer) - Refactor transaction payload helpers and step contexts - Fix key omission, mapping validation, and typing issues - Update docs, coverage, and formatting - Remove pnpm lockfile and align dependency management
1 parent 44d0e1c commit fb36b6e

File tree

12 files changed

+651
-192
lines changed

12 files changed

+651
-192
lines changed

FEATURE_COVERAGE.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Feature Coverage Matrix
22

3-
> **Last Updated:** 2026-02-23
3+
> **Last Updated:** 2026-03-20
44
>
55
> This file tracks implementation status of behavioral specifications across all SDK
66
> implementations. Check boxes indicate that step definitions exist and tests pass for that
@@ -33,7 +33,7 @@
3333
3434
| SDK | Required (P0) | Preferred (P1) | Optional (P2) | Total | Notes |
3535
| ---------- | -------------- | -------------- | ------------- | ------- | ------------------------------------------------ |
36-
| TypeScript | ~494/791 (62%) | included | included | 494/791 | 49 failed, 37 undefined; API tests need network |
36+
| TypeScript | ~494/796 (62%) | included | included | 494/796 | 49 failed, 37 undefined; API tests need network |
3737
| Go | 333/791 (42%) | included | included | 333/791 | 136 failed, 312 pending, 10 undefined |
3838
| Rust | 723/791 (91%) | included | included | 723/791 | 56 skipped, 12 failed (network/benchmarks) |
3939
| .NET | 478/808 (59%) | included | included | 478/808 | 330 failures (last verified 2026-01-28) |
@@ -756,7 +756,7 @@
756756
| Feature | TS | Go | Rust | Java | Kotlin | Python | .NET | C++ | Swift |
757757
| ------------------------------- | -------- | ------- | -------- | -------- | ------ | ------- | -------- | ------- | ----- |
758758
| **error-handling** `@required` | 🟡 28/30 | 🟡 1/30 | 🟡 27/30 | 🟡 28/30 | 🟡 | ❌ 0/30 | 🟡 15/30 | ❌ 0/30 ||
759-
| **simulation** `@preferred` | 🟡 21/26 | ❌ 0/26 | ❌ 0/26 | ❌ 0/26 || ❌ 0/26 | ❌ 0/26 | ❌ 0/26 ||
759+
| **simulation** `@preferred` | 🟡 26/31 | ❌ 0/31 | ❌ 0/31 | ❌ 0/31 || ❌ 0/31 | ❌ 0/31 | ❌ 0/31 ||
760760
| **multi-agent** `@optional` | ✅ 20/20 | ❌ 0/20 | ❌ 0/20 | ❌ 0/20 || ❌ 0/20 | ❌ 0/20 | ❌ 0/20 ||
761761
| **fee-payer** `@optional` | ✅ 23/23 | ❌ 0/23 | ❌ 0/23 | ❌ 0/23 || ❌ 0/23 | ❌ 0/23 | ❌ 0/23 ||
762762
| **multi-signature** `@optional` | ✅ 23/23 | ❌ 0/23 | ❌ 0/23 | ❌ 0/23 || ❌ 0/23 | ❌ 0/23 | ❌ 0/23 ||
@@ -818,16 +818,21 @@
818818
| 14 | Simulate at specific version ||||||||||
819819
| 15 | Simulate with gas override ||||||||||
820820
| 16 | Simulate with gas price override ||||||||||
821-
| 17 | Simulate multi-agent tx ||||||||||
822-
| 18 | Simulate fee payer tx ||||||||||
823-
| 19 | Simulation doesn't commit ||||||||||
824-
| 20 | Simulation may differ from exec ||||||||||
825-
| 21 | Simulation with current seq ||||||||||
826-
| 22 | Simulate multiple txs ||||||||||
827-
| 23 | Simulate tx sequence ||||||||||
828-
| 24 | Simulation network error ||||||||||
829-
| 25 | Invalid tx for simulation ||||||||||
830-
| 26 | Simulation timeout ||||||||||
821+
| 17 | Multi-agent sim with sender pubkey ||||||||||
822+
| 18 | Multi-agent sim without pubkeys ||||||||||
823+
| 19 | Multi-agent sim with partial checks ||||||||||
824+
| 20 | Reject malformed signer key mapping ||||||||||
825+
| 21 | Multi-agent sim includes changes ||||||||||
826+
| 22 | Multi-agent + fee payer sim (skip) ||||||||||
827+
| 23 | Multi-agent + fee payer sim (keys) ||||||||||
828+
| 24 | Simulation doesn't commit ||||||||||
829+
| 25 | Simulation may differ from exec ||||||||||
830+
| 26 | Simulation with current seq ||||||||||
831+
| 27 | Simulate multiple txs ||||||||||
832+
| 28 | Simulate tx sequence ||||||||||
833+
| 29 | Simulation network error ||||||||||
834+
| 30 | Invalid tx for simulation ||||||||||
835+
| 31 | Simulation timeout ||||||||||
831836

832837
### multi-agent.feature `@optional`
833838

@@ -1099,9 +1104,10 @@ cd tests/rust && cargo test --test specs
10991104

11001105
**Test Results (non-network, non-performance):**
11011106

1102-
- 580 scenarios: 494 passed, 49 failed, 37 undefined
1107+
- 585 scenarios in this bucket (`not (@api-clients or @network or @performance)`)
11031108
- 2220 steps: 1993 passed, 49 failed, 132 undefined, 46 skipped
1104-
- api-clients: Requires network (133 additional scenarios, timeout in CI)
1109+
- api-clients: Requires network (133 scenarios for
1110+
`@api-clients and not (@network or @performance)`; 193 total `@api-clients` scenarios)
11051111

11061112
**Mocked Tests (not real implementations):**
11071113

features/06-advanced/simulation.feature

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,17 +134,64 @@ Feature: Transaction Simulation
134134
# Multi-Agent Simulation
135135
# =============================================================================
136136
@preferred
137-
Scenario: Simulate multi-agent transaction
137+
Scenario: Simulate multi-agent tx with senderPublicKey + secondarySignersPublicKeys
138+
Given a multi-agent simulation transaction with 1 secondary signer
139+
And sender public key is provided for simulation
140+
And secondary signer public keys are provided for simulation
141+
When I simulate the multi-agent transaction
142+
Then simulation should work
143+
And auth-key checks should run for all provided signers
144+
145+
@preferred
146+
Scenario: Simulate multi-agent tx with no public keys (skip auth-key checks)
138147
Given a multi-agent transaction
139-
When I simulate it
148+
And no signer public keys are provided for simulation
149+
When I simulate the multi-agent transaction
150+
Then simulation should work
151+
And auth-key checks should be skipped
152+
153+
@preferred
154+
Scenario: Simulate multi-agent tx with partial auth-key checks using undefined placeholders
155+
Given a multi-agent simulation transaction with 3 secondary signers
156+
And sender public key is provided for simulation
157+
And secondary signer public keys include undefined placeholders
158+
When I simulate the multi-agent transaction
159+
Then simulation should work
160+
And auth-key checks should run only for provided signer slots
161+
162+
@preferred
163+
Scenario: Reject simulation input when secondary signer key mapping is malformed
164+
Given a multi-agent simulation transaction with 2 secondary signers
165+
And secondary signer public key mapping has wrong length
166+
When I try to simulate
167+
Then I should get validation error before simulation even runs
168+
169+
@preferred
170+
Scenario: Simulation result includes changes/events across involved accounts
171+
Given a multi-agent transaction
172+
And a simulation result covering sender and secondary accounts
173+
When I inspect the multi-agent simulation result
140174
Then simulation should work
141175
And show changes for all involved accounts
176+
And it should include events for involved accounts
142177

143178
@preferred
144-
Scenario: Simulate fee payer transaction
145-
Given a fee payer transaction
146-
When I simulate it
147-
Then gas should be charged to fee payer
179+
Scenario: Simulate multi-agent + fee payer transaction with skipped auth-key checks
180+
Given a fee payer transaction with sender, secondary, and sponsor
181+
And no signer public keys are provided for simulation
182+
When I simulate the multi-agent fee-payer transaction
183+
Then simulation should work
184+
And gas should be charged to fee payer
185+
And simulation should reflect that
186+
187+
@preferred
188+
Scenario: Simulate multi-agent + fee payer transaction with explicit signer key checks
189+
Given a fee payer transaction with sender, secondary, and sponsor
190+
And sender, secondary, and fee payer public keys are provided for simulation
191+
When I simulate the multi-agent fee-payer transaction
192+
Then simulation should work
193+
And gas should be charged to fee payer
194+
And auth-key checks should run for all provided signers
148195
And simulation should reflect that
149196

150197
# =============================================================================

features/06-advanced/spec.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,25 @@ message = SHA3-256("APTOS::RawTransactionWithData") || bcs(FeePayer {
164164

165165
---
166166

167+
## Transaction Simulation Semantics (Preferred - P1)
168+
169+
### Description
170+
171+
Simulation validates transaction structure and executes VM logic without committing state.
172+
173+
### Multi-Agent / Fee Payer Simulation Rules
174+
175+
- Simulation input MAY include sender, secondary signer, and fee payer public keys for optional
176+
authentication key checks.
177+
- SDKs MAY allow simulation when signer public keys are omitted; in this mode, authentication key
178+
checks are skipped.
179+
- For multi-agent simulation, SDKs MAY support partial checks by allowing undefined placeholders in
180+
secondary signer public key slots.
181+
- Simulation does NOT require full transaction authenticator validation and does NOT imply the
182+
transaction can be submitted successfully on-chain.
183+
184+
---
185+
167186
## Keyless Accounts (Optional - P2)
168187

169188
### Description

specifications/07-advanced-features.md

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -553,11 +553,50 @@ Transaction simulation executes a transaction without committing, returning expe
553553
```
554554
simulate(
555555
raw_txn: RawTransaction,
556-
sender_public_key: PublicKey
556+
sender_public_key: Option<PublicKey>
557557
) -> Result<SimulationResult, Error>
558558
```
559559

560-
### 6.3 Simulation Result [P1]
560+
`sender_public_key` is optional for SDKs that support skipping authentication key checks in
561+
simulation.
562+
563+
### 6.3 Multi-Agent / Fee Payer Simulation Inputs [P1]
564+
565+
For multi-agent and fee payer simulation, SDKs MAY accept additional signer public keys to run
566+
authentication key checks before simulation.
567+
568+
**Language-agnostic pseudocode shape (illustrative):**
569+
570+
```
571+
simulate_multi_agent(
572+
raw_txn: RawTransaction,
573+
secondary_signer_addresses: Vec<AccountAddress>,
574+
sender_public_key: Option<PublicKey>,
575+
secondary_signers_public_keys: Option<Vec<Option<PublicKey>>>
576+
) -> Result<SimulationResult, Error>
577+
578+
simulate_fee_payer(
579+
raw_txn: RawTransaction,
580+
secondary_signer_addresses: Vec<AccountAddress>,
581+
fee_payer_address: AccountAddress,
582+
sender_public_key: Option<PublicKey>,
583+
secondary_signers_public_keys: Option<Vec<Option<PublicKey>>>,
584+
fee_payer_public_key: Option<PublicKey>
585+
) -> Result<SimulationResult, Error>
586+
```
587+
588+
**Requirements:**
589+
590+
1. If signer public keys (sender / secondary / fee payer) are provided, SDK **MUST** check provided
591+
signer/address mappings via authentication keys.
592+
2. If signer public keys are omitted, SDK **MAY** skip authentication key checks and still simulate.
593+
3. For multi-agent simulation, SDK **MAY** support partial checks by allowing `undefined`/`None`
594+
entries in secondary signer key slots.
595+
4. Malformed key mappings (e.g., address count and key-slot count mismatch) **MUST** fail validation
596+
before simulation request execution.
597+
5. Simulation **MUST NOT** be treated as full transaction authenticator validation.
598+
599+
### 6.4 Simulation Result [P1]
561600

562601
| Field | Type | Description |
563602
| --------- | ----------- | ------------------------------ |
@@ -567,7 +606,7 @@ simulate(
567606
| changes | Vec<Change> | State changes that would occur |
568607
| events | Vec<Event> | Events that would be emitted |
569608

570-
### 6.4 Use Cases [P1]
609+
### 6.5 Use Cases [P1]
571610

572611
1. **Gas Estimation:** Determine gas needed before submission
573612
2. **Error Preview:** Check for errors before submission
@@ -718,7 +757,7 @@ Test vectors in `test-vectors/multi-sig.json`:
718757
- `features/06-advanced/multi-agent.feature` - 22 multi-agent scenarios
719758
- `features/06-advanced/fee-payer.feature` - 23 fee payer scenarios
720759
- `features/06-advanced/keyless.feature` - 28 keyless scenarios
721-
- `features/06-advanced/simulation.feature` - 23 simulation scenarios
760+
- `features/06-advanced/simulation.feature` - 31 simulation scenarios
722761
- `features/06-advanced/codegen.feature` - 30 codegen scenarios
723762

724763
### 10.3 Test Vectors

tests/rust/src/steps/network_steps.rs

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,11 @@ fn when_measure_get_tx_by_hash(world: &mut TestWorld, iterations: usize) {
586586
if let Some(ref aptos) = world.aptos_client {
587587
let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
588588
// Use hash from world or a known genesis transaction
589-
let hash_str = world.hex_string.as_deref().unwrap_or(
590-
"0x0000000000000000000000000000000000000000000000000000000000000001",
591-
);
592-
if let Ok(hash) =
593-
aptos_sdk::types::HashValue::from_hex(hash_str.trim_start_matches("0x"))
594-
{
589+
let hash_str = world
590+
.hex_string
591+
.as_deref()
592+
.unwrap_or("0x0000000000000000000000000000000000000000000000000000000000000001");
593+
if let Ok(hash) = aptos_sdk::types::HashValue::from_hex(hash_str.trim_start_matches("0x")) {
595594
for _ in 0..iterations {
596595
let start = Instant::now();
597596
let _ =
@@ -630,12 +629,7 @@ fn when_measure_query_tokens(world: &mut TestWorld, iterations: usize) {
630629
let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
631630
for _ in 0..iterations {
632631
let start = Instant::now();
633-
let _ = rt.block_on(async {
634-
aptos
635-
.fullnode()
636-
.get_account_resources(address)
637-
.await
638-
});
632+
let _ = rt.block_on(async { aptos.fullnode().get_account_resources(address).await });
639633
world
640634
.benchmark_timings
641635
.push(start.elapsed().as_micros() as u64);
@@ -654,12 +648,7 @@ fn when_measure_query_transactions(world: &mut TestWorld, iterations: usize) {
654648
let start = Instant::now();
655649
// Note: get_account_transactions is not available in the current SDK version.
656650
// Using get_account_resources as an account-level fullnode query benchmark instead.
657-
let _ = rt.block_on(async {
658-
aptos
659-
.fullnode()
660-
.get_account_resources(address)
661-
.await
662-
});
651+
let _ = rt.block_on(async { aptos.fullnode().get_account_resources(address).await });
663652
world
664653
.benchmark_timings
665654
.push(start.elapsed().as_micros() as u64);
@@ -676,12 +665,7 @@ fn when_measure_query_fa_balances(world: &mut TestWorld, iterations: usize) {
676665
let rt = tokio::runtime::Runtime::new().expect("Failed to create runtime");
677666
for _ in 0..iterations {
678667
let start = Instant::now();
679-
let _ = rt.block_on(async {
680-
aptos
681-
.fullnode()
682-
.get_account_resources(address)
683-
.await
684-
});
668+
let _ = rt.block_on(async { aptos.fullnode().get_account_resources(address).await });
685669
world
686670
.benchmark_timings
687671
.push(start.elapsed().as_micros() as u64);
@@ -722,9 +706,7 @@ fn when_measure_submit_transfers(world: &mut TestWorld, count: usize) {
722706
world.benchmark_timings.clear();
723707

724708
// Network-dependent: only run real submissions when we have a funded account and client
725-
if let (Some(ref aptos), Some(ref account)) =
726-
(&world.aptos_client, &world.ed25519_account)
727-
{
709+
if let (Some(ref aptos), Some(ref account)) = (&world.aptos_client, &world.ed25519_account) {
728710
use aptos_sdk::transaction::{EntryFunction, TransactionBuilder, TransactionPayload};
729711
use aptos_sdk::ChainId;
730712

@@ -798,9 +780,7 @@ fn when_measure_submit_and_wait(world: &mut TestWorld, count: usize) {
798780
world.benchmark_timings.clear();
799781

800782
// Full round-trip requires real network - record timings only when available
801-
if let (Some(ref aptos), Some(ref account)) =
802-
(&world.aptos_client, &world.ed25519_account)
803-
{
783+
if let (Some(ref aptos), Some(ref account)) = (&world.aptos_client, &world.ed25519_account) {
804784
use aptos_sdk::transaction::{EntryFunction, TransactionBuilder, TransactionPayload};
805785
use aptos_sdk::ChainId;
806786

@@ -843,9 +823,7 @@ fn when_measure_full_flow(world: &mut TestWorld, count: usize) {
843823
world.benchmark_timings.clear();
844824

845825
// Full flow measurement requires real network
846-
if let (Some(ref aptos), Some(ref account)) =
847-
(&world.aptos_client, &world.ed25519_account)
848-
{
826+
if let (Some(ref aptos), Some(ref account)) = (&world.aptos_client, &world.ed25519_account) {
849827
use aptos_sdk::transaction::{
850828
builder::sign_transaction, EntryFunction, TransactionBuilder, TransactionPayload,
851829
};

0 commit comments

Comments
 (0)