-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[CLI][Move] support public structs/enums as txn args in CLI #18591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: teng/public-struct-txn-args
Are you sure you want to change the base?
Conversation
|
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
88acdd2 to
dfd47a0
Compare
fc1c26d to
d31e4d0
Compare
155b20c to
398a435
Compare
f37ad1b to
b30d55a
Compare
170c1ef to
e22d876
Compare
8f27993 to
dd7a3d2
Compare
bfc10dc to
ff0ca1f
Compare
dd7a3d2 to
966306b
Compare
ff0ca1f to
12c09c2
Compare
12c09c2 to
f69d861
Compare
966306b to
a6c536f
Compare
aptos-move/move-examples/cli-e2e-tests/common/sources/cli_e2e_tests.move
Outdated
Show resolved
Hide resolved
f69d861 to
faf499b
Compare
810bb48 to
fb023bd
Compare
0904383 to
1158753
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
fb023bd to
f37f6c7
Compare
1158753 to
081bb83
Compare
f37f6c7 to
a354293
Compare
081bb83 to
4c7df6b
Compare
a354293 to
8fd8607
Compare
4c7df6b to
2918dfa
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this 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, | ||
| }) | ||
| } |
There was a problem hiding this comment.
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)
2918dfa to
d5cbfd1
Compare
8fd8607 to
05382f0
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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).
05382f0 to
12b69f7
Compare
d5cbfd1 to
21f01a8
Compare
There was a problem hiding this 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, | ||
| ) |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
| } else { | ||
| // Use synchronous parsing for command-line arguments | ||
| self.try_into() | ||
| } |
There was a problem hiding this comment.
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.
| @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) |
There was a problem hiding this comment.
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)
12b69f7 to
82b0fc2
Compare
21f01a8 to
eeed102
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
eeed102 to
d8acf6c
Compare
82b0fc2 to
428e114
Compare
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
d8acf6c to
879aeae
Compare
428e114 to
4f22f38
Compare
There was a problem hiding this 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(), | ||
| )); | ||
| } |
There was a problem hiding this comment.
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.
| .parse_value_by_type(&field_type, field_value, depth) | ||
| .await?; | ||
| encoded_fields.extend(encoded_value); | ||
| } |
There was a problem hiding this comment.
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)
| [cli_path, "node", "run-local-testnet", "--with-faucet", "--force-restart", "--assume-yes"], | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| universal_newlines=True, |
There was a problem hiding this comment.
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.
There was a problem hiding this 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, |
There was a problem hiding this comment.
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.
| ))); | ||
| } | ||
| vec![field_values.values().next().unwrap().clone()] | ||
| }; |
There was a problem hiding this comment.
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.



Description
This PR:
How Has This Been Tested?
Newly added cli e2e tests.
Key Areas to Review
Type of Change
Which Components or Systems Does This Change Impact?
Checklist
Note
Medium Risk
Medium risk because it changes
move run/simulate/viewargument 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, andmove viewnow support JSON args that reference fully-qualified Move types and pass public structs/enums (including nested types andOption<T>in both legacy vector and new variant formats) by fetching on-chain module bytecode/ABI and encoding arguments to BCS via a new asyncStructArgParser.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-testnetmode 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.