-
Notifications
You must be signed in to change notification settings - Fork 11.4k
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
Feature/basic move call arg linter #508
Conversation
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.
Love seeing this, will be a huge UX improvement! And really like the way you have made it work for both the CLI and the REST API's.
Suggested changes I feel strongly about:
- Sharing function signature validation code and function resolution code with the adapter, even if it requires doing some refactoring here or in another PR that we land first. The validation code especially is very intricate, and we're going to be making some changes there. I want to make sure we only have to maintain one copy of it.
- Refactoring the BCS conversion to remove the vector length prefix hack
- Making some strides toward a
SuiJSON
type (or similar) that represents the input language for Sui API users + can be specified later.
const HEX_PREFIX: &str = "0x"; | ||
|
||
#[derive(Debug)] | ||
pub struct MoveFunctionComponents { |
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.
pub struct MoveFunctionComponents { | |
pub struct MoveFunctionCall { |
let function_signature = get_expected_fn_signature(package, module.clone(), function.clone())?; | ||
|
||
// Now we check that the args are proper | ||
// More checks are done in the adapter |
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.
CC @awelc to see if there's a way to reuse the adapter's implementation of the checks here. E.g., it looks like adapter::resolve_and_type_check
could probably be refactored to separate the function signature checks from the arg value checks. Would be nice to have only one copy of this logic if we can swing it!
pub function: Identifier, | ||
pub object_args: Vec<ObjectID>, | ||
pub pure_args_serialized: Vec<Vec<u8>>, | ||
// TODO: add type args checks |
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.
Is there a blocker that makes it difficult to implement this?
|
||
// We can encode U8 Vector as string in 2 ways | ||
// 1. If it starts with 0x, we treat it as hex strings, where each pair is a byte | ||
// 2. If it does not start with 0x, we treat each character as an ASCII endoced byte |
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.
// 2. If it does not start with 0x, we treat each character as an ASCII endoced byte | |
// 2. If it does not start with 0x, we treat each character as an ASCII encoded byte |
// If starts with 0x, treat as hex vector? | ||
hex::decode(s.trim_start_matches(HEX_PREFIX))? | ||
} else { | ||
check_if_ascii(s.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.
We don't know that a Move call is going to turn a string into ASCII. It might turn it into UTF8, or (very commonly for immutable content like NFT metadata) not even look at the bytes. Thus, I think we shouldn't assume or enforce that the string is ASCII here--just converting it to bytes with no validation is fine/preferred.
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.
@sblackshear does this not conflict with (1b) in your requirements here? #451
It says to Fail if the string contains non-ASCII 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.
It does--apologies for that! Please ignore the advice of past me; did not think this all the way through.
let transformed = match arg { | ||
Value::String(s) => { | ||
let mut s = s.trim().to_lowercase(); | ||
if !s.starts_with(HEX_PREFIX) { |
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.
I think it would be simpler to pick one of "always need the prefix" or "never need the prefix" instead of supporting both.
Because object ID's can also appear in the pure args (e.g., if the sig is foo(o: Object, object_id: vector<u8>)
) "always need the prefix" seems a bit clearer/more consistent? It seems simple enough to explain that
- every time you write an object ID, you need a
0x
prefix - if you want to write some hex encoded bytes that are not an object ID, you also need a
0x
prefix
} | ||
|
||
/// Get the expected function signature from the package, module, and identifier | ||
fn get_expected_fn_signature( |
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.
I think this should be in impl MovePackage
(or at least in object.rs) rather than here--finding a function in a package will be very common.
Seems like another opportunity for code sharing with the adapter as well.
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.
Agreed.
impl MovePackage
requires making MovePackage
a NewType (currently its aliasing BTreeMap), which I want to do, but expands the scope of this PR.
For now I can just make this a function in object.rs and improve MovePackage
later
fn resolve_pure_arg(curr_val: &Value, expected_type: &Type) -> Result<Vec<u8>> { | ||
if !is_primitive(expected_type) { | ||
return Err(anyhow!( | ||
"Unexpected arg type {:?}. Only primitive types are allowed", |
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 arg type {:?}. Only primitive types are allowed", | |
"Unexpected arg type {}. Only primitive types are allowed", |
This will give you Display
printing, which is much nicer. As a general principle, if we're returning an error that users will see (not just ones that Sui devs will see), we probably want to implement the Display
trait and use display printing so they don't have to see the ugliness of Rust debug printing.
pub module: Identifier, | ||
pub function: Identifier, | ||
pub object_args: Vec<ObjectID>, | ||
pub pure_args_serialized: Vec<Vec<u8>>, |
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.
pub pure_args_serialized: Vec<Vec<u8>>, | |
pub pure_args_bcs: Vec<Vec<u8>>, |
@sblackshear based on our discussions here I think I should start with the following (in no order) before merging this PR, otherwise I'll have to come back around and delete code:
With these changes, adding the parser should be cleaner. |
Closing in favor of |
Related to: #451
WIP: simplify serialization if possible
Why we need this:
What this is:
Example fn signature:
but instead use something like this:
What this is not:
Next steps (future PR):
is_primitive
inmove_binary_format::Type::
rather than here