Skip to content

Conversation

@rahxephon89
Copy link
Contributor

@rahxephon89 rahxephon89 commented Feb 4, 2026

Description

This PR:

  1. adds support of public structs/enums as txn args for Aptos CLI;
  2. adds support of running local net for cli e2e tests without using a docker.

How Has This Been Tested?

Newly added cli e2e tests.

Key Areas to Review

Type of Change

  • New feature
  • Bug fix
  • Breaking change
  • Performance improvement
  • Refactoring
  • Dependency update
  • Documentation update
  • Tests

Which Components or Systems Does This Change Impact?

  • Validator Node
  • Full Node (API, Indexer, etc.)
  • Move/Aptos Virtual Machine
  • Aptos Framework
  • Aptos CLI/SDK
  • Developer Infrastructure
  • Move Compiler
  • Other (specify)

Checklist

  • I have read and followed the CONTRIBUTING doc
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I identified and added all stakeholders and component owners affected by this change as reviewers
  • I tested both happy and unhappy path of the functionality
  • I have made corresponding changes to the documentation

Note

Medium Risk
Medium risk because it changes move run/simulate/view argument parsing to conditionally perform REST lookups and BCS encoding for complex types, which could affect transaction construction and error handling.

Overview
Adds struct/enum txn-arg support in the CLI (JSON input). move run, move simulate, and move view now support JSON args that reference fully-qualified Move types and pass public structs/enums (including nested types and Option<T> in both legacy vector and new variant formats) by fetching on-chain module bytecode/ABI and encoding arguments to BCS via a new async StructArgParser.

Expands CLI e2e coverage and runner flexibility. Introduces a new Move test package and Python e2e cases for struct/enum args, pins Move CLI e2e compilation/publish to language version 2.4, and adds a --use-local-testnet mode to the e2e harness to run against a locally started testnet without Docker.

Written by Cursor Bugbot for commit 879aeae. This will update automatically on new commits. Configure here.

Copy link
Contributor Author

rahxephon89 commented Feb 4, 2026

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@rahxephon89 rahxephon89 changed the title cli support [experiment] support public structs/enums as txn args in CLI Feb 4, 2026
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch 2 times, most recently from fc1c26d to d31e4d0 Compare February 5, 2026 01:06
@rahxephon89 rahxephon89 force-pushed the teng/txn-args-CLI branch 2 times, most recently from 155b20c to 398a435 Compare February 5, 2026 04:25
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch 2 times, most recently from f37ad1b to b30d55a Compare February 5, 2026 05:31
@rahxephon89 rahxephon89 force-pushed the teng/txn-args-CLI branch 2 times, most recently from 170c1ef to e22d876 Compare February 5, 2026 07:00
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch 2 times, most recently from 8f27993 to dd7a3d2 Compare February 5, 2026 18:45
@rahxephon89 rahxephon89 force-pushed the teng/txn-args-CLI branch 2 times, most recently from bfc10dc to ff0ca1f Compare February 5, 2026 19:01
@rahxephon89 rahxephon89 changed the title [experiment] support public structs/enums as txn args in CLI [CLI][Move] support public structs/enums as txn args in CLI Feb 5, 2026
@rahxephon89 rahxephon89 marked this pull request as ready for review February 5, 2026 19:07
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from dd7a3d2 to 966306b Compare February 5, 2026 22:35
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 966306b to a6c536f Compare February 6, 2026 03:09
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 810bb48 to fb023bd Compare February 9, 2026 01:24
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} else {
// Module not in cache, need to fetch
}
} // Release read lock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache miss causes redundant network refetch for nonexistent types

Low Severity

In verify_struct_exists, when a module IS already cached and the struct/enum is confirmed NOT to exist (via ABI or compiled module check), the code falls through past line 118 and re-fetches the entire module from the network at line 126. The struct_exists = false result from a definitive ABI/compiled check is treated the same as the "couldn't check yet" case, causing a redundant REST API call that will reach the same conclusion.

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from fb023bd to f37f6c7 Compare February 9, 2026 08:54
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from f37f6c7 to a354293 Compare February 9, 2026 09:47
@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from a354293 to 8fd8607 Compare February 9, 2026 09:51
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

if '"success": true' not in response.stdout:
raise TestError(f"{error_msg}: Transaction did not execute successfully on-chain")
except Exception as e:
raise TestError(error_msg) from e
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exception handler catches its own raised TestError

Low Severity

In run_move_function_with_json, the except Exception block at line 68 catches all exceptions, including the TestError raised at line 67 by the on-chain success check. This replaces the specific message "{error_msg}: Transaction did not execute successfully on-chain" with just the generic error_msg, making it harder to distinguish a transaction execution failure from a CLI invocation failure during debugging. The success check belongs outside the try block, or the except needs to re-raise TestError without wrapping.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

_vector_depth: 0,
arg: bcs_bytes,
})
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated Option array-to-variant conversion logic across files

Low Severity

The Option<T> array-to-variant conversion logic (empty array → "None", single-element array → "Some" with field "0") is duplicated between EntryFunctionArgumentsJSON::parse_option_vector_format in types.rs and StructArgParser::parse_option_value in struct_arg_parser.rs. The comment on parse_option_value even acknowledges this: "This helper extracts repeated logic for Option handling." The core mapping logic and error messages are nearly identical across both locations, differing only in return type wrapping (ArgWithType vs Vec<u8>).

Additional Locations (1)

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 8fd8607 to 05382f0 Compare February 9, 2026 10:20
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} else {
// Module not in cache, need to fetch
}
} // Release read lock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache miss causes redundant REST fetch for existing modules

Low Severity

In verify_struct_exists, when a module IS cached and the struct is conclusively NOT found (via ABI or compiled module check), struct_exists is false and the code falls through to re-fetch the module from chain. The comment says "fall through to deserialize" but this path also triggers when the ABI/bytecode definitively confirms the type doesn't exist. This results in a wasted REST API call and also overwrites the cache entry (potentially losing a previously deserialized compiled module if the fresh fetch only has ABI).

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 05382f0 to 12b69f7 Compare February 10, 2026 00:45
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Subprocess pipes never drained causing potential deadlock

Medium Severity

The localnet subprocess is created with stdout=subprocess.PIPE and stderr=subprocess.PIPE, but neither pipe is ever read during normal test execution. Per Python docs, this can deadlock: when the OS pipe buffer fills up (~64KB on Linux), the child process blocks on writes. Since a running Aptos node can easily produce more than 64KB of log output during a full test run, the localnet could freeze, causing test failures or hangs. Using subprocess.DEVNULL or redirecting to log files would avoid this.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} else {
// Module not in cache, need to fetch
}
} // Release read lock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache logic conflates "not found" with "couldn't check"

Medium Severity

In verify_struct_exists, when the module is cached and the struct is definitively not found via ABI or compiled module check, struct_exists is set to false — the same value used when verification isn't possible (no ABI, no compiled form). The code then falls through to re-fetch the module from chain, making an unnecessary API call before eventually returning the same "not found" error. The three distinct false cases — ABI says absent, compiled module says absent, and can't check — are conflated, causing redundant network round-trips for the error path.

Fix in Cursor Fix in Web

} else {
// Use synchronous parsing for command-line arguments
self.try_into()
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST client eagerly created for all JSON files

Low Severity

parse_with_optional_client creates a REST client whenever json_file.is_some(), but the client is only actually used when the JSON contains struct/enum args (detected later in check_input_style_async via has_struct_or_enum_args()). This means JSON files with only primitive args now require a configured REST URL, which is a behavioral regression for commands like Simulate --local where the REST endpoint may not be needed. The doc comment's claim that "The get_client closure is only called if async parsing is needed" is misleading — it's called for all JSON files.

Fix in Cursor Fix in Web

@test_case
def test_option_variant_format(run_helper: RunHelper, test_name=None):
"""Test Option<T> with new variant format: {"None": {}} and {"Some": {"0": value}}."""
account_address = str(run_helper.get_account_info().account_address)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused account_address variables in test functions

Low Severity

In test_option_variant_format and test_option_legacy_format, the account_address variable is assigned from run_helper.get_account_info().account_address but never referenced — the JSON content uses hardcoded "0x1::option::Option<u64>" strings instead. This is dead code left over from copy-pasting other test functions that do use the variable (like test_enum_simple_variant).

Additional Locations (1)

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 12b69f7 to 82b0fc2 Compare February 10, 2026 07:45
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} else {
// Module not in cache, need to fetch
}
} // Release read lock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant re-fetch when cached module lacks struct

Low Severity

In verify_struct_exists, when the module is already cached and the ABI confirms a struct does not exist, the code falls through to re-fetch the entire module from the network (line 126). This redundant REST call will produce the same result. The fall-through logic doesn't distinguish "module cached but type missing" from "module not cached" or "need to deserialize," so it always re-fetches and re-caches. The three cases (not in cache, needs deserialization, and genuinely missing type) deserve separate handling.

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 82b0fc2 to 428e114 Compare February 10, 2026 23:52
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

} else {
// Module not in cache, need to fetch
}
} // Release read lock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cache hit with absent struct still re-fetches module

Low Severity

In verify_struct_exists, when the module is cached and the struct is verified absent via ABI or compiled module, struct_exists is false and the code falls through to re-fetch the module from chain instead of returning an error immediately. The comment on line 119 says "If we couldn't verify," but the same fallthrough path executes when verification succeeded and confirmed the struct doesn't exist.

Fix in Cursor Fix in Web

@rahxephon89 rahxephon89 force-pushed the teng/public-struct-txn-args branch from 428e114 to 4f22f38 Compare February 11, 2026 09:18
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

This should not happen if the CLI is properly configured."
.to_string(),
));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Struct args still fail in some commands

Medium Severity

EntryFunctionArgumentsJSON::try_into() now rejects any argument type containing ::. Commands that still call synchronous EntryFunctionArguments::try_into() never reach the new async parser, so --json-file with struct/enum args fails outside updated paths.

Fix in Cursor Fix in Web

.parse_value_by_type(&field_type, field_value, depth)
.await?;
encoded_fields.extend(encoded_value);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected struct fields are silently ignored

Medium Severity

The new parser validates required fields but never rejects extra keys in input objects. In both construct_struct_argument and construct_enum_argument, unknown fields are ignored, so malformed JSON can be accepted and encoded as a different value than the user supplied.

Additional Locations (1)

Fix in Cursor Fix in Web

[cli_path, "node", "run-local-testnet", "--with-faucet", "--force-restart", "--assume-yes"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Background localnet can block on log pipes

Medium Severity

subprocess.Popen(...) starts run-local-testnet with both stdout and stderr set to PIPE, but those streams are never drained during normal execution. A chatty localnet can fill OS pipe buffers and block, causing startup or later tests to hang unpredictably.

Fix in Cursor Fix in Web

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

[cli_path, "node", "run-local-testnet", "--with-faucet", "--force-restart", "--assume-yes"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Localnet process can block on full pipes

Medium Severity

subprocess.Popen starts run-local-testnet with stdout and stderr set to PIPE, but the code never drains those streams while the process runs. Since localnet is long-lived and chatty, pipe buffers can fill and block the child process, causing startup hangs or flaky timeouts in --use-local-testnet mode.

Fix in Cursor Fix in Web

)));
}
vec![field_values.values().next().unwrap().clone()]
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option Some field name is never validated

Low Severity

For Option::Some in variant-object format, parsing ignores field keys and takes field_values.values().next(). Inputs like {"Some":{"wrong_key":"42"}} are accepted as valid Some(42). This silently accepts malformed JSON instead of validating the expected "0" field for the variant payload.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants