diff --git a/doc/move_code/objects_tutorial/sources/Ch1_2/ColorObject.move b/doc/move_code/objects_tutorial/sources/Ch1_2/ColorObject.move index 4aa3b9538c193..381aaf3ac7fc5 100644 --- a/doc/move_code/objects_tutorial/sources/Ch1_2/ColorObject.move +++ b/doc/move_code/objects_tutorial/sources/Ch1_2/ColorObject.move @@ -24,7 +24,7 @@ module Tutorial::ColorObject { } } - public fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { + public(script) fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { let color_object = new(red, green, blue, ctx); Transfer::transfer(color_object, TxContext::sender(ctx)) } @@ -35,12 +35,12 @@ module Tutorial::ColorObject { // == Functions covered in Chapter 2 == - public fun delete(object: ColorObject, _ctx: &mut TxContext) { + public(script) fun delete(object: ColorObject, _ctx: &mut TxContext) { let ColorObject { id, red: _, green: _, blue: _ } = object; ID::delete(id); } - public fun transfer(object: ColorObject, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(object: ColorObject, recipient: address, _ctx: &mut TxContext) { Transfer::transfer(object, recipient) } } @@ -131,4 +131,4 @@ module Tutorial::ColorObjectTests { assert!(TestScenario::can_take_object(scenario), 0); }; } -} \ No newline at end of file +} diff --git a/doc/src/build/move.md b/doc/src/build/move.md index 775c3e801142b..9f1d045e77081 100644 --- a/doc/src/build/move.md +++ b/doc/src/build/move.md @@ -246,7 +246,7 @@ simplest entry functions is defined in the SUI implement gas object transfer: ```rust -public fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { +public(script) fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { ... } ``` @@ -631,7 +631,7 @@ point of view of a Sui developer. First, let us create sword creation and transfer and put them into the `M1.move` file: ``` rust - public fun sword_create(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun sword_create(magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { use Sui::Transfer; use Sui::TxContext; // create a sword @@ -644,7 +644,7 @@ sword creation and transfer and put them into the `M1.move` file: Transfer::transfer(sword, recipient); } - public fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) { + public(script) fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) { use Sui::Transfer; // transfer the sword Transfer::transfer(sword, recipient); @@ -852,7 +852,7 @@ function to take the forge as a parameter and to update the number of created swords at the end of the function: ``` rust - public fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { ... forge.swords_created = forge.swords_created + 1; } @@ -928,7 +928,7 @@ We can also transfer an object to be owned by another object. Note that the owne Once an object is owned by another object, it is required that for any such object referenced in the entry function, its owner must also be one of the argument objects. For instance, if we have a chain of ownership: account address `Addr1` owns object `a`, object `a` owns object `b`, and `b` owns object `c`, in order to use object `c` in a Move call, the entry function must also include both `b` and `a`, and the signer of the transaction must be `Addr1`, like this: ``` // signer of ctx is Addr1. -public fun entry_function(a: &A, b: &B, c: &mut C, ctx: &mut TxContext); +public(script) fun entry_function(a: &A, b: &B, c: &mut C, ctx: &mut TxContext); ``` A common pattern of object owning another object is to have a field in the parent object to track the ID of the child object. It is important to ensure that we keep such a field's value consistent with the actual ownership relationship. For example, we do not end up in a situation where the parent's child field contains an ID pointing to object A, while in fact the parent owns object B. To ensure the consistency, we defined a custom type called `ChildRef` to represent object ownership. Whenever an object is transferred to another object, a `ChildRef` instance is created to uniquely identify the ownership. The library implementation ensures that the `ChildRef` goes side-by-side with the child object so that we never lose track or mix up objects. diff --git a/doc/src/build/programming-with-objects/ch1-object-basics.md b/doc/src/build/programming-with-objects/ch1-object-basics.md index 3ed95f66dfb5f..c80ba0ae26b5f 100644 --- a/doc/src/build/programming-with-objects/ch1-object-basics.md +++ b/doc/src/build/programming-with-objects/ch1-object-basics.md @@ -64,7 +64,7 @@ Below is the code that creates a new `ColorObject` and makes it owned by the sen use Sui::Transfer; // This is an entry function that can be called directly by a Transaction. -public fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { +public(script) fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { let color_object = new(red, green, blue, ctx); Transfer::transfer(color_object, TxContext::sender(ctx)) } diff --git a/doc/src/build/programming-with-objects/ch2-using-objects.md b/doc/src/build/programming-with-objects/ch2-using-objects.md index e307211587b68..d6434b6720550 100644 --- a/doc/src/build/programming-with-objects/ch2-using-objects.md +++ b/doc/src/build/programming-with-objects/ch2-using-objects.md @@ -19,7 +19,7 @@ struct ColorObject has key { Now let's add this function: ```rust /// Copies the values of `from_object` into `into_object`. -public fun copy_into(from_object: &ColorObject, into_object, &mut ColorObject, _ctx: &mut TxContext) { +public(script) fun copy_into(from_object: &ColorObject, into_object, &mut ColorObject, _ctx: &mut TxContext) { into_object.red = from_object.red; into_object.green = from_object.green; into_object.blue = from_object.blue; @@ -49,7 +49,7 @@ public fun delete(versioned_id: VersionedID); ``` Let's define a function in the `ColorObject` module that allows us to delete the object: ```rust - public fun delete(object: ColorObject, _ctx: &mut TxContext) { + public(script) fun delete(object: ColorObject, _ctx: &mut TxContext) { let ColorObject { id, red: _, green: _, blue: _ } = object; ID::delete(id); } @@ -83,7 +83,7 @@ The first part is the same as what we have seen in [Chapter 1](./ch1-object-basi #### Option 2. Transfer the object The owner of the object may want to transfer it to another account. To support this, the `ColorObject` module will need to define a `transfer` API: ```rust -public fun transfer(object: ColorObject, recipient: address, _ctx: &mut TxContext) { +public(script) fun transfer(object: ColorObject, recipient: address, _ctx: &mut TxContext) { Transfer::transfer(object, recipient) } ``` diff --git a/doc/src/build/wallet.md b/doc/src/build/wallet.md index 6f913fc970a42..a27321cc10dce 100644 --- a/doc/src/build/wallet.md +++ b/doc/src/build/wallet.md @@ -137,7 +137,7 @@ connection to the authorities. You can also connect the wallet to the Sui network via a [Rest Gateway](rest-api.md#start-local-rest-server); To use the rest gateway, update `wallet.conf`'s `gateway` section to: ```json -{ +{ ... "gateway": { "rest":"http://127.0.0.1:5001" @@ -719,7 +719,7 @@ for the first look at Move source code and a description of the following function we will be calling in this tutorial: ```rust -public fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { +public(script) fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { Coin::transfer(c, Address::new(recipient)) } ``` diff --git a/doc/src/explore/prototypes.md b/doc/src/explore/prototypes.md index fc5637d8aa582..b71ba79e3893c 100644 --- a/doc/src/explore/prototypes.md +++ b/doc/src/explore/prototypes.md @@ -69,7 +69,7 @@ POST `/call` with body: "function": "update_monster_stats", "args": [ "0x{{player_id}}", - "0x{{farm_id}}", + "0x{{farm_id}}", "0x{{pet_monsters}}", "0x{{monster_id}}", {{monster_level}}, @@ -107,11 +107,11 @@ GET /object_info?objectId={{monster_id}} // ID of the applied cosmetic at this slot applied_monster_cosmetic_0_id: Option, // ID of the applied cosmetic at this slot - applied_monster_cosmetic_1_id: Option, + applied_monster_cosmetic_1_id: Option, } // Create a Monster and add it to the Farm's collection of Monsters - public fun create_monster(_player: &mut Player, + public(script) fun create_monster(_player: &mut Player, farm: &mut Farm, pet_monsters_c: &mut Collection::Collection, monster_name: vector, @@ -140,7 +140,7 @@ GET /object_info?objectId={{monster_id}} } // Creates a basic Monster object - public fun create_monster_( + public(script) fun create_monster_( monster_name: vector, monster_img_index: u64, breed: u8, @@ -171,7 +171,7 @@ GET /object_info?objectId={{monster_id}} ``` // Update the attributes of a monster - public fun update_monster_stats( + public(script) fun update_monster_stats( _player: &mut Player, _farm: &mut Farm, _pet_monsters: &mut Collection::Collection, diff --git a/doc/src/explore/tutorials.md b/doc/src/explore/tutorials.md index c69638fdc61db..6b8faa84c79bb 100644 --- a/doc/src/explore/tutorials.md +++ b/doc/src/explore/tutorials.md @@ -28,7 +28,7 @@ gas units) times the price of gas in the SUI currency (i.e. the gas price). ## Gather accounts and gas objects -In that new terminal, let us take a look at the account addresses we own in +In that new terminal, let us take a look at the account addresses we own in our wallet: ``` $ wallet addresses @@ -183,7 +183,7 @@ export GAME=F1B8161BD97D3CD6627E739AD675089C5ACFB452 By convention, Player X goes first. Player X wants to put a mark at the center of the gameboard ((1, 1)). This needs to take two steps. First Player X creates a Mark object with the placement intention and send it to the admin. We will call the `send_mark_to_game` function in `TicTacToe`, whose signature looks like this: ``` -public fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext); +public(script) fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext); ``` The `cap` argument will be Player X's capability object (XCAP), and `game_address` argument will be the admin's address (ADMIN): ``` @@ -209,7 +209,7 @@ Mutated Objects: The above call created a Mark object, with ID `AE3CE9176F1A8C1F21D922722486DF667FA00394`, and it was sent to the admin. The admin can now place the mark on the gameboard. The function to place the mark looks like this: ``` -public fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext); +public(script) fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext); ``` The first argument is the game board, and the second argument is the mark the admin just received from the player. We will call this function (replace the second argument with the Mark object ID above): ``` diff --git a/sui/Cargo.toml b/sui/Cargo.toml index 36199929d5a35..0cc59abfe786f 100644 --- a/sui/Cargo.toml +++ b/sui/Cargo.toml @@ -41,6 +41,7 @@ sui-adapter = { path = "../sui_programmability/adapter" } sui-framework = { path = "../sui_programmability/framework" } sui-network = { path = "../network_utils" } sui-types = { path = "../sui_types" } +sui-verifier = { path = "../sui_programmability/verifier" } rustyline = "9.1.2" rustyline-derive = "0.6.0" diff --git a/sui/src/sui_json.rs b/sui/src/sui_json.rs index dbc69f66866ff..59a2e0d80aa42 100644 --- a/sui/src/sui_json.rs +++ b/sui/src/sui_json.rs @@ -1,19 +1,26 @@ // Copyright (c) 2022, Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use anyhow::anyhow; -use move_core_types::{account_address::AccountAddress, identifier::Identifier}; +use anyhow::{anyhow, bail}; +use move_core_types::{ + identifier::Identifier, + value::{MoveTypeLayout, MoveValue}, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::VecDeque; use sui_types::{ base_types::{decode_bytes_hex, ObjectID, SuiAddress}, - move_package::is_primitive, object::Object, }; +use sui_verifier::entry_points_verifier::is_object; // Alias the type names for clarity -use move_binary_format::normalized::{Function as MoveFunction, Type as NormalizedMoveType}; +use move_binary_format::{ + access::ModuleAccess, + binary_views::BinaryIndexedView, + file_format::{SignatureToken, Visibility}, +}; use serde_json::Value as JsonValue; const HEX_PREFIX: &str = "0x"; @@ -22,17 +29,6 @@ const HEX_PREFIX: &str = "0x"; #[path = "unit_tests/sui_json.rs"] mod base_types_tests; -#[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize)] -pub enum IntermediateValue { - Bool(bool), - U8(u8), - U64(u64), - U128(u128), - Address(SuiAddress), - ObjectID(ObjectID), - Vector(Vec), -} - #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, JsonSchema)] pub struct SuiJsonValue(JsonValue); impl SuiJsonValue { @@ -58,89 +54,37 @@ impl SuiJsonValue { Ok(Self(json_value)) } - pub fn to_bcs_bytes(&self, typ: &NormalizedMoveType) -> Result, anyhow::Error> { - let intermediate_val = Self::to_intermediate_value(&self.0, typ)?; - - fn inner_serialize( - inter_val: IntermediateValue, - ty: &NormalizedMoveType, - ) -> Result, anyhow::Error> { - let ser = match (inter_val.clone(), ty) { - (IntermediateValue::Bool(b), NormalizedMoveType::Bool) => bcs::to_bytes(&b)?, - (IntermediateValue::U8(n), NormalizedMoveType::U8) => bcs::to_bytes(&n)?, - (IntermediateValue::U64(n), NormalizedMoveType::U64) => bcs::to_bytes(&n)?, - (IntermediateValue::U128(n), NormalizedMoveType::U128) => bcs::to_bytes(&n)?, - - (IntermediateValue::Address(a), NormalizedMoveType::Address) => { - bcs::to_bytes(&AccountAddress::from(a))? - } - - // Not currently used - // (IntermediateValue::ObjectID(a), NormalizedMoveType::Address) => { - // bcs::to_bytes(&AccountAddress::from(a))? - // } - (IntermediateValue::Vector(v), NormalizedMoveType::Vector(move_type)) => { - let mut inner_ser = vec![]; - let arr_len = v.len(); - for i in v { - // Serialize each - inner_ser.append(&mut inner_serialize(i, move_type)?); - } - // The data is already serialized, so ideally we just append - // First serialize the types like they u8s - // We use this to create the ULEB128 length prefix - let u8vec = vec![0u8; arr_len]; - let mut ser_container = bcs::to_bytes::>(&u8vec)?; - // Delete the zeroes - ser_container.truncate(ser_container.len() - arr_len); - // Append the actual data data - ser_container.append(&mut inner_ser); - - ser_container - } - _ => { - return Err(anyhow!( - "Unable to serialize {:?}. Expected {}", - inter_val, - ty - )) - } - }; - Ok(ser) - } - inner_serialize(intermediate_val, typ) + pub fn to_bcs_bytes(&self, ty: &MoveTypeLayout) -> Result, anyhow::Error> { + let move_value = Self::to_move_value(&self.0, ty)?; + MoveValue::simple_serialize(&move_value) + .ok_or_else(|| anyhow!("Unable to serialize {:?}. Expected {}", move_value, ty)) } pub fn to_json_value(&self) -> JsonValue { self.0.clone() } - fn to_intermediate_value( - val: &JsonValue, - typ: &NormalizedMoveType, - ) -> Result { - let new_serde_value = match (val, typ.clone()) { + fn to_move_value(val: &JsonValue, ty: &MoveTypeLayout) -> Result { + Ok(match (val, ty) { // Bool to Bool is simple - (JsonValue::Bool(b), NormalizedMoveType::Bool) => IntermediateValue::Bool(*b), + (JsonValue::Bool(b), MoveTypeLayout::Bool) => MoveValue::Bool(*b), // In constructor, we have already checked that the JSON number is unsigned int of at most U64 // Hence it is okay to unwrap() numbers - (JsonValue::Number(n), NormalizedMoveType::U8) => { - IntermediateValue::U8(u8::try_from(n.as_u64().unwrap())?) - } - (JsonValue::Number(n), NormalizedMoveType::U64) => { - IntermediateValue::U64(n.as_u64().unwrap()) + (JsonValue::Number(n), MoveTypeLayout::U8) => { + MoveValue::U8(u8::try_from(n.as_u64().unwrap())?) } + (JsonValue::Number(n), MoveTypeLayout::U64) => MoveValue::U64(n.as_u64().unwrap()), // u8, u64, u128 can be encoded as String - (JsonValue::String(s), NormalizedMoveType::U8) => { - IntermediateValue::U8(u8::try_from(convert_string_to_u128(s.as_str())?)?) + (JsonValue::String(s), MoveTypeLayout::U8) => { + MoveValue::U8(u8::try_from(convert_string_to_u128(s.as_str())?)?) } - (JsonValue::String(s), NormalizedMoveType::U64) => { - IntermediateValue::U64(u64::try_from(convert_string_to_u128(s.as_str())?)?) + (JsonValue::String(s), MoveTypeLayout::U64) => { + MoveValue::U64(u64::try_from(convert_string_to_u128(s.as_str())?)?) } - (JsonValue::String(s), NormalizedMoveType::U128) => { - IntermediateValue::U128(convert_string_to_u128(s.as_str())?) + (JsonValue::String(s), MoveTypeLayout::U128) => { + MoveValue::U128(convert_string_to_u128(s.as_str())?) } // U256 Not allowed for now @@ -150,9 +94,9 @@ impl SuiJsonValue { // 2. If it does not start with 0x, we treat each character as an ASCII encoded byte // We have to support both for the convenience of the user. This is because sometime we need Strings as arg // Other times we need vec of hex bytes for address. Issue is both Address and Strings are represented as Vec in Move call - (JsonValue::String(s), NormalizedMoveType::Vector(t)) => { - if *t != NormalizedMoveType::U8 { - return Err(anyhow!("Cannot convert string arg {s} to {typ}")); + (JsonValue::String(s), MoveTypeLayout::Vector(t)) => { + if !matches!(&**t, &MoveTypeLayout::U8) { + return Err(anyhow!("Cannot convert string arg {s} to {ty}")); } let vec = if s.starts_with(HEX_PREFIX) { // If starts with 0x, treat as hex vector @@ -161,31 +105,29 @@ impl SuiJsonValue { // Else raw bytes s.as_bytes().to_vec() }; - IntermediateValue::Vector(vec.iter().map(|q| IntermediateValue::U8(*q)).collect()) + MoveValue::Vector(vec.iter().copied().map(MoveValue::U8).collect()) } // We have already checked that the array is homogeneous in the constructor - (JsonValue::Array(a), NormalizedMoveType::Vector(t)) => { + (JsonValue::Array(a), MoveTypeLayout::Vector(inner)) => { // Recursively build an IntermediateValue array - IntermediateValue::Vector( + MoveValue::Vector( a.iter() - .map(|i| Self::to_intermediate_value(i, &t)) - .collect::, _>>()?, + .map(|i| Self::to_move_value(i, inner)) + .collect::, _>>()?, ) } - (JsonValue::String(s), NormalizedMoveType::Address) => { + (JsonValue::String(s), MoveTypeLayout::Address) => { let s = s.trim().to_lowercase(); if !s.starts_with(HEX_PREFIX) { return Err(anyhow!("Address hex string must start with 0x.",)); } let r: SuiAddress = decode_bytes_hex(s.trim_start_matches(HEX_PREFIX))?; - IntermediateValue::Address(r) + MoveValue::Address(r.into()) } - _ => return Err(anyhow!("Unexpected arg {val} for expected type {typ}")), - }; - - Ok(new_serde_value) + _ => return Err(anyhow!("Unexpected arg {val} for expected type {ty}")), + }) } } @@ -254,35 +196,52 @@ fn is_homogeneous_rec(curr_q: &mut VecDeque<&JsonValue>) -> bool { is_homogeneous_rec(&mut next_q) } -fn check_and_serialize_pure_args( - args: &[SuiJsonValue], - start: usize, - end_exclusive: usize, - function_signature: MoveFunction, +fn check_and_serialize_pure_args<'a>( + pure_args_and_params: impl IntoIterator, ) -> Result>, anyhow::Error> { - // The vector of serialized arguments - let mut pure_args_serialized = vec![]; - - // Iterate through the pure args - for (idx, curr) in args - .iter() - .enumerate() - .skip(start) - .take(end_exclusive - start) - { - // The type the function expects at this position - let expected_pure_arg_type = &function_signature.parameters[idx]; - - // Check that the args are what we expect or can be converted - // Then return the serialized bcs value - match curr.to_bcs_bytes(expected_pure_arg_type) { - Ok(a) => { - pure_args_serialized.push(a.clone()); + pure_args_and_params + .into_iter() + .map(|(arg, param)| { + let move_type_layout = make_prim_move_type_layout(param)?; + // Check that the args are what we expect or can be converted + // Then return the serialized bcs value + match arg.to_bcs_bytes(&move_type_layout) { + Ok(a) => Ok(a), + Err(e) => { + return Err(anyhow!( + "Unable to parse arg at type {}. Got error: {:?}", + move_type_layout, + e + )) + } } - Err(e) => return Err(anyhow!("Unable to parse arg at pos: {}, err: {:?}", idx, e)), + }) + .collect() +} + +fn make_prim_move_type_layout(param: &SignatureToken) -> Result { + Ok(match param { + SignatureToken::Bool => MoveTypeLayout::Bool, + SignatureToken::U8 => MoveTypeLayout::U8, + SignatureToken::U64 => MoveTypeLayout::U64, + SignatureToken::U128 => MoveTypeLayout::U128, + SignatureToken::Address => MoveTypeLayout::Address, + SignatureToken::Signer => MoveTypeLayout::Signer, + SignatureToken::Vector(inner) => { + MoveTypeLayout::Vector(Box::new(make_prim_move_type_layout(inner)?)) } - } - Ok(pure_args_serialized) + SignatureToken::Struct(_) + | SignatureToken::StructInstantiation(_, _) + | SignatureToken::Reference(_) + | SignatureToken::MutableReference(_) + | SignatureToken::TypeParameter(_) => { + debug_assert!( + false, + "Should be unreachable. Args should be primitive types only" + ); + bail!("Could not serialize argument of type {:?}", param) + } + }) } fn resolve_object_args( @@ -327,19 +286,50 @@ fn resolve_object_args( /// This is because we have special types which we need to specify in other formats pub fn resolve_move_function_args( package: &Object, - module: Identifier, + module_ident: Identifier, function: Identifier, combined_args_json: Vec, ) -> Result<(Vec, Vec>), anyhow::Error> { // Extract the expected function signature - let function_signature = package + let module = package .data .try_as_package() .ok_or_else(|| anyhow!("Cannot get package from object"))? - .get_function_signature(&module, &function)?; + .deserialize_module(&module_ident)?; + let function_str = function.as_ident_str(); + let fdef = module + .function_defs + .iter() + .find(|fdef| { + module.identifier_at(module.function_handle_at(fdef.function).name) == function_str + }) + .ok_or_else(|| { + anyhow!( + "Could not resolve function {} in module {}", + function, + module_ident + ) + })?; + let function_signature = module.function_handle_at(fdef.function); + let parameters = &module.signature_at(function_signature.parameters).0; + + if fdef.visibility != Visibility::Script { + bail!( + "{}::{} does not have public(script) visibility", + module.self_id(), + function, + ) + } + if !function_signature.type_parameters.is_empty() { + bail!( + "{}::{} has type arguments, which are not yet supported in sui_json", + module.self_id(), + function, + ) + } // Lengths have to match, less one, due to TxContext - let expected_len = function_signature.parameters.len() - 1; + let expected_len = parameters.len() - 1; if combined_args_json.len() != expected_len { return Err(anyhow!( "Expected {} args, found {}", @@ -351,10 +341,15 @@ pub fn resolve_move_function_args( // Object args must always precede the pure/primitive args, so extract those first // Find the first non-object args, which marks the start of the pure args // Find the first pure/primitive type - let pure_args_start = function_signature - .parameters + let view = BinaryIndexedView::Module(&module); + let pessimistic_type_args = &function_signature.type_parameters; + debug_assert!( + pessimistic_type_args.is_empty(), + "TODO support type arguments" + ); + let pure_args_start = parameters .iter() - .position(is_primitive) + .position(|t| !is_object(&view, pessimistic_type_args, t).unwrap()) .unwrap_or(expected_len); // Everything to the left of pure args must be object args @@ -363,12 +358,9 @@ pub fn resolve_move_function_args( let obj_args = resolve_object_args(&combined_args_json, 0, pure_args_start)?; // Check that the pure args are valid or can be made valid - let pure_args_serialized = check_and_serialize_pure_args( - &combined_args_json, - pure_args_start, - expected_len, - function_signature, - )?; + let pure_args = &combined_args_json[pure_args_start..expected_len]; + let pure_params = ¶meters[pure_args_start..expected_len]; + let pure_args_serialized = check_and_serialize_pure_args(pure_args.iter().zip(pure_params))?; Ok((obj_args, pure_args_serialized)) } diff --git a/sui/src/unit_tests/data/custom_genesis_package_1/sources/CustomObjectTemplate.move b/sui/src/unit_tests/data/custom_genesis_package_1/sources/CustomObjectTemplate.move index 86e697d1d0bd4..0120f62846540 100644 --- a/sui/src/unit_tests/data/custom_genesis_package_1/sources/CustomObjectTemplate.move +++ b/sui/src/unit_tests/data/custom_genesis_package_1/sources/CustomObjectTemplate.move @@ -85,7 +85,7 @@ module Examples::CustomObjectTemplate { /// input objects + created objects + emitted events, increments the /// sequence number each object, creates a hash that commits to the /// outputs, etc. - public fun main( + public(script) fun main( to_read: &Object, to_write: &mut Object, to_consume: Object, diff --git a/sui/src/unit_tests/data/custom_genesis_package_1/sources/TicTacToe.move b/sui/src/unit_tests/data/custom_genesis_package_1/sources/TicTacToe.move index fe7afc33d0e1b..4069837d25f08 100644 --- a/sui/src/unit_tests/data/custom_genesis_package_1/sources/TicTacToe.move +++ b/sui/src/unit_tests/data/custom_genesis_package_1/sources/TicTacToe.move @@ -59,7 +59,7 @@ module Examples::TicTacToe { } /// `x_address` and `o_address` are the account address of the two players. - public fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { + public(script) fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { // TODO: Validate sender address, only GameAdmin can create games. let id = TxContext::new_id(ctx); @@ -94,7 +94,7 @@ module Examples::TicTacToe { /// Generate a new mark intended for location (row, col). /// This new mark is not yet placed, just transferred to the game. - public fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext) { + public(script) fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext) { if (row > 2 || col > 2) { abort INVALID_LOCATION }; @@ -108,7 +108,7 @@ module Examples::TicTacToe { Transfer::transfer(mark, game_address); } - public fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext) { + public(script) fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext) { // If we are placing the mark at the wrong turn, or if game has ended, // destroy the mark. let addr = get_cur_turn_address(game); @@ -138,7 +138,7 @@ module Examples::TicTacToe { } } - public fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { + public(script) fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { let TicTacToe { id, gameboard, cur_turn: _, game_status: _, x_address: _, o_address: _ } = game; while (Vector::length(&gameboard) > 0) { let row = Vector::pop_back(&mut gameboard); @@ -156,12 +156,12 @@ module Examples::TicTacToe { ID::delete(id); } - public fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { + public(script) fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { let Trophy { id } = trophy; ID::delete(id); } - public fun delete_cap(cap: MarkMintCap, _ctx: &mut TxContext) { + public(script) fun delete_cap(cap: MarkMintCap, _ctx: &mut TxContext) { let MarkMintCap { id, game_id: _, remaining_supply: _ } = cap; ID::delete(id); } diff --git a/sui/src/unit_tests/data/custom_genesis_package_1/sources/TrustedCoin.move b/sui/src/unit_tests/data/custom_genesis_package_1/sources/TrustedCoin.move index 0f574a74eece9..007ba3187dc04 100644 --- a/sui/src/unit_tests/data/custom_genesis_package_1/sources/TrustedCoin.move +++ b/sui/src/unit_tests/data/custom_genesis_package_1/sources/TrustedCoin.move @@ -20,12 +20,12 @@ module Examples::TrustedCoin { Transfer::transfer(treasury_cap, TxContext::sender(ctx)) } - public fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { + public(script) fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { let coin = Coin::mint(amount, treasury_cap, ctx); Coin::transfer(coin, TxContext::sender(ctx)); } - public fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { Coin::transfer_cap(treasury_cap, recipient); } diff --git a/sui/src/unit_tests/data/dummy_modules_publish/sources/TrustedCoin.move b/sui/src/unit_tests/data/dummy_modules_publish/sources/TrustedCoin.move index 0f574a74eece9..007ba3187dc04 100644 --- a/sui/src/unit_tests/data/dummy_modules_publish/sources/TrustedCoin.move +++ b/sui/src/unit_tests/data/dummy_modules_publish/sources/TrustedCoin.move @@ -20,12 +20,12 @@ module Examples::TrustedCoin { Transfer::transfer(treasury_cap, TxContext::sender(ctx)) } - public fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { + public(script) fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { let coin = Coin::mint(amount, treasury_cap, ctx); Coin::transfer(coin, TxContext::sender(ctx)); } - public fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { Coin::transfer_cap(treasury_cap, recipient); } diff --git a/sui/src/unit_tests/sui_json.rs b/sui/src/unit_tests/sui_json.rs index 8c634f1ce049a..912a601213d50 100644 --- a/sui/src/unit_tests/sui_json.rs +++ b/sui/src/unit_tests/sui_json.rs @@ -3,8 +3,9 @@ use std::path::Path; -use move_binary_format::normalized::Type; -use move_core_types::{account_address::AccountAddress, identifier::Identifier}; +use move_core_types::{ + account_address::AccountAddress, identifier::Identifier, value::MoveTypeLayout, +}; use serde_json::{json, Value}; use sui_adapter::{self, genesis::clone_genesis_packages}; use sui_types::{ @@ -97,104 +98,104 @@ fn test_basic_args_linter_pure_args() { // Expected Bool match ( Value::from(true), - Type::Bool, + MoveTypeLayout::Bool, Some(bcs::to_bytes(&true).unwrap()), ), // Expected U8 match ( Value::from(9u8), - Type::U8, + MoveTypeLayout::U8, Some(bcs::to_bytes(&9u8).unwrap()), ), // U64 value less than 256 can be used as U8 ( Value::from(9u64), - Type::U8, + MoveTypeLayout::U8, Some(bcs::to_bytes(&9u8).unwrap()), ), // U8 value encoded as str ( Value::from("89"), - Type::U8, + MoveTypeLayout::U8, Some(bcs::to_bytes(&89u8).unwrap()), ), // U8 value encoded as str promoted to U64 ( Value::from("89"), - Type::U64, + MoveTypeLayout::U64, Some(bcs::to_bytes(&89u64).unwrap()), ), // U64 value encoded as str ( Value::from("890"), - Type::U64, + MoveTypeLayout::U64, Some(bcs::to_bytes(&890u64).unwrap()), ), // U128 value encoded as str ( Value::from(format!("{u128_val}")), - Type::U128, + MoveTypeLayout::U128, Some(bcs::to_bytes(&u128_val).unwrap()), ), // U8 value encoded as hex str ( Value::from("0x12"), - Type::U8, + MoveTypeLayout::U8, Some(bcs::to_bytes(&0x12u8).unwrap()), ), // U8 value encoded as hex str promoted to U64 ( Value::from("0x12"), - Type::U64, + MoveTypeLayout::U64, Some(bcs::to_bytes(&0x12u64).unwrap()), ), // U64 value encoded as hex str ( Value::from("0x890"), - Type::U64, + MoveTypeLayout::U64, Some(bcs::to_bytes(&0x890u64).unwrap()), ), // U128 value encoded as hex str ( Value::from(format!("0x{:02x}", u128_val)), - Type::U128, + MoveTypeLayout::U128, Some(bcs::to_bytes(&u128_val).unwrap()), ), // Space not allowed - (Value::from(" 9"), Type::U8, None), + (Value::from(" 9"), MoveTypeLayout::U8, None), // Hex must start with 0x - (Value::from("AB"), Type::U8, None), + (Value::from("AB"), MoveTypeLayout::U8, None), // Too large - (Value::from("123456789"), Type::U8, None), + (Value::from("123456789"), MoveTypeLayout::U8, None), // Too large - (Value::from("123456789123456789123456789123456789"), Type::U64, None), + (Value::from("123456789123456789123456789123456789"), MoveTypeLayout::U64, None), // Too large - (Value::from("123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"), Type::U128, None), + (Value::from("123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789123456789"), MoveTypeLayout::U128, None), // U64 value greater than 255 cannot be used as U8 - (Value::from(900u64), Type::U8, None), + (Value::from(900u64), MoveTypeLayout::U8, None), // floats cannot be used as U8 - (Value::from(0.4f32), Type::U8, None), + (Value::from(0.4f32), MoveTypeLayout::U8, None), // floats cannot be used as U64 - (Value::from(3.4f32), Type::U64, None), + (Value::from(3.4f32), MoveTypeLayout::U64, None), // Negative cannot be used as Unsigned - (Value::from(-1), Type::U8, None), + (Value::from(-1), MoveTypeLayout::U8, None), // u8 vector can be gotten from string ( Value::from(good_ascii_str), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), Some(bcs::to_bytes(&good_ascii_str.as_bytes()).unwrap()), ), // u8 vector from bad string ( Value::from(good_utf8_str), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), Some(bcs::to_bytes(&good_utf8_str.as_bytes()).unwrap()), ), // u8 vector from hex repr ( Value::from(good_hex_val), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), Some( bcs::to_bytes(&hex::decode(&good_hex_val.trim_start_matches(HEX_PREFIX)).unwrap()) .unwrap(), @@ -203,25 +204,25 @@ fn test_basic_args_linter_pure_args() { // u8 vector from bad hex repr ( Value::from(bad_hex_val), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), None, ), // u8 vector from u8 array ( json!([1, 2, 3, 4, 5, 6, 7]), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), Some(bcs::to_bytes(&vec![1u8, 2u8, 3u8, 4u8, 5u8, 6u8, 7u8]).unwrap()), ), // u8 vector from heterogenous array ( json!([1, 2, 3, true, 5, 6, 7]), - Type::Vector(Box::new(Type::U8)), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), None, ), // Vector of vector of u8s ( json!([[1, 2, 3], [], [3, 4, 5, 6, 7]]), - Type::Vector(Box::new(Type::Vector(Box::new(Type::U8)))), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)))), Some( bcs::to_bytes(&vec![ vec![1u8, 2u8, 3u8], @@ -234,7 +235,7 @@ fn test_basic_args_linter_pure_args() { // U64 nest ( json!([[1111, 2, 3], [], [300, 4, 5, 6, 7]]), - Type::Vector(Box::new(Type::Vector(Box::new(Type::U64)))), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U64)))), Some( bcs::to_bytes(&vec![ vec![1111u64, 2u64, 3u64], @@ -247,14 +248,14 @@ fn test_basic_args_linter_pure_args() { // U64 deep nest, bad because heterogenous array ( json!([[[9, 53, 434], [0], [300]], [], [300, 4, 5, 6, 7]]), - Type::Vector(Box::new(Type::Vector(Box::new(Type::U64)))), + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U64)))), None, ), // U64 deep nest, good ( json!([[[9, 53, 434], [0], [300]], [], [[332], [4, 5, 6, 7]]]), - Type::Vector(Box::new(Type::Vector(Box::new(Type::Vector(Box::new( - Type::U64, + MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new(MoveTypeLayout::Vector(Box::new( + MoveTypeLayout::U64, )))))), Some( bcs::to_bytes(&vec![ diff --git a/sui/src/wallet_commands.rs b/sui/src/wallet_commands.rs index edd4a8944091a..54cdd783bbc0c 100644 --- a/sui/src/wallet_commands.rs +++ b/sui/src/wallet_commands.rs @@ -16,6 +16,7 @@ use move_core_types::parser::parse_type_tag; use serde::Serialize; use tracing::info; +use sui_adapter::adapter::resolve_and_type_check; use sui_core::gateway_state::gateway_responses::{ MergeCoinResponse, PublishResponse, SplitCoinResponse, SwitchResponse, }; @@ -24,7 +25,6 @@ use sui_framework::build_move_package_to_bytes; use sui_types::base_types::{decode_bytes_hex, ObjectID, ObjectRef, SuiAddress}; use sui_types::gas_coin::GasCoin; use sui_types::messages::{CertifiedTransaction, ExecutionStatus, Transaction, TransactionEffects}; -use sui_types::move_package::resolve_and_type_check; use sui_types::object::ObjectRead::Exists; use sui_types::object::{Object, ObjectRead}; @@ -287,10 +287,13 @@ impl WalletCommands { let sender = gas_object.owner.get_owner_address()?; // Pass in the objects for a deeper check - // We can technically move this to impl MovePackage + let compiled_module = package_obj + .data + .try_as_package() + .ok_or_else(|| anyhow!("Cannot get package from object"))? + .deserialize_module(module)?; resolve_and_type_check( - package_obj, - module, + &compiled_module, function, type_args, input_objs, diff --git a/sui_core/src/authority.rs b/sui_core/src/authority.rs index 3693bd6ad8e04..707504c16ffc8 100644 --- a/sui_core/src/authority.rs +++ b/sui_core/src/authority.rs @@ -12,14 +12,14 @@ use move_core_types::{ language_storage::{ModuleId, StructTag}, resolver::{ModuleResolver, ResourceResolver}, }; -use move_vm_runtime::native_functions::NativeFunctionTable; +use move_vm_runtime::{move_vm::MoveVM, native_functions::NativeFunctionTable}; use std::sync::atomic::AtomicUsize; use std::{ collections::{BTreeMap, HashMap, HashSet, VecDeque}, pin::Pin, sync::Arc, }; -use sui_adapter::adapter::{self, SuiMoveVM}; +use sui_adapter::adapter; use sui_types::{ base_types::*, batch::UpdateItem, @@ -82,7 +82,7 @@ pub struct AuthorityState { /// Move native functions that are available to invoke _native_functions: NativeFunctionTable, - move_vm: Arc, + move_vm: Arc, /// The database pub(crate) _database: Arc, // TODO: remove pub @@ -601,8 +601,10 @@ impl AuthorityState { name, secret, _native_functions: native_functions.clone(), - move_vm: adapter::new_move_vm(native_functions) - .expect("We defined natives to not fail here"), + move_vm: Arc::new( + adapter::new_move_vm(native_functions) + .expect("We defined natives to not fail here"), + ), _database: store.clone(), batch_channels: tx, batch_notifier: Arc::new( @@ -679,8 +681,6 @@ impl AuthorityState { natives, &mut gas_status, )?; - let vm = SuiMoveVM::new(vm); - adapter::store_package_and_init_modules( &mut temporary_store, &vm, diff --git a/sui_core/src/execution_engine.rs b/sui_core/src/execution_engine.rs index 1a5730906793a..19fd7c150bb64 100644 --- a/sui_core/src/execution_engine.rs +++ b/sui_core/src/execution_engine.rs @@ -4,7 +4,8 @@ use std::{collections::BTreeSet, sync::Arc}; use crate::authority::AuthorityTemporaryStore; -use move_vm_runtime::native_functions::NativeFunctionTable; +use move_core_types::language_storage::ModuleId; +use move_vm_runtime::{move_vm::MoveVM, native_functions::NativeFunctionTable}; use sui_adapter::adapter; use sui_types::{ base_types::{SuiAddress, TransactionDigest, TxContext}, @@ -24,7 +25,7 @@ pub fn execute_transaction_to_effects( transaction: Transaction, transaction_digest: TransactionDigest, objects_by_kind: Vec<(InputObjectKind, Object)>, - move_vm: &Arc, + move_vm: &Arc, native_functions: &NativeFunctionTable, gas_status: SuiGasStatus, ) -> SuiResult { @@ -72,7 +73,7 @@ fn execute_transaction( transaction: Transaction, mut objects_by_kind: Vec<(InputObjectKind, Object)>, tx_ctx: &mut TxContext, - move_vm: &Arc, + move_vm: &Arc, native_functions: &NativeFunctionTable, mut gas_status: SuiGasStatus, ) -> ExecutionStatus { @@ -95,13 +96,14 @@ fn execute_transaction( SingleTransactionKind::Call(c) => { let mut inputs: Vec<_> = object_input_iter.by_ref().take(input_size).collect(); // unwraps here are safe because we built `inputs` - let package = inputs.pop().unwrap(); + // TODO don't load and push the package object here in the first place + let _package = inputs.pop().unwrap(); + let module_id = ModuleId::new(c.package.0.into(), c.module.clone()); result = adapter::execute( move_vm, temporary_store, native_functions, - &package, - &c.module, + module_id, &c.function, c.type_arguments.clone(), inputs, diff --git a/sui_core/src/unit_tests/data/hero/sources/Hero.move b/sui_core/src/unit_tests/data/hero/sources/Hero.move index 05569ae99dd3d..d1fec94617f0a 100644 --- a/sui_core/src/unit_tests/data/hero/sources/Hero.move +++ b/sui_core/src/unit_tests/data/hero/sources/Hero.move @@ -114,7 +114,7 @@ module Examples::Hero { /// Slay the `boar` with the `hero`'s sword, get experience. /// Aborts if the hero has 0 HP or is not strong enough to slay the boar - public fun slay(hero: &mut Hero, boar: Boar, ctx: &mut TxContext) { + public(script) fun slay(hero: &mut Hero, boar: Boar, ctx: &mut TxContext) { let Boar { id: boar_id, strength: boar_strength, hp } = boar; let hero_strength = hero_strength(hero); let boar_hp = hp; @@ -222,7 +222,7 @@ module Examples::Hero { } } - public fun acquire_hero(payment: Coin, ctx: &mut TxContext) { + public(script) fun acquire_hero(payment: Coin, ctx: &mut TxContext) { let sword = create_sword(payment, ctx); let hero = create_hero(sword, ctx); Transfer::transfer(hero, TxContext::sender(ctx)) diff --git a/sui_core/src/unit_tests/data/hero/sources/TrustedCoin.move b/sui_core/src/unit_tests/data/hero/sources/TrustedCoin.move index 0f574a74eece9..007ba3187dc04 100644 --- a/sui_core/src/unit_tests/data/hero/sources/TrustedCoin.move +++ b/sui_core/src/unit_tests/data/hero/sources/TrustedCoin.move @@ -20,12 +20,12 @@ module Examples::TrustedCoin { Transfer::transfer(treasury_cap, TxContext::sender(ctx)) } - public fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { + public(script) fun mint(treasury_cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext) { let coin = Coin::mint(amount, treasury_cap, ctx); Coin::transfer(coin, TxContext::sender(ctx)); } - public fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { Coin::transfer_cap(treasury_cap, recipient); } diff --git a/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move b/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move index 6379e7ef12f1e..b9c3f49b51602 100644 --- a/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move +++ b/sui_core/src/unit_tests/data/object_owner/sources/ObjectOwner.move @@ -16,21 +16,21 @@ module ObjectOwner::ObjectOwner { id: VersionedID, } - public fun create_child(ctx: &mut TxContext) { + public(script) fun create_child(ctx: &mut TxContext) { Transfer::transfer( Child { id: TxContext::new_id(ctx) }, TxContext::sender(ctx), ); } - public fun create_parent(ctx: &mut TxContext) { + public(script) fun create_parent(ctx: &mut TxContext) { Transfer::transfer( Parent { id: TxContext::new_id(ctx), child: Option::none() }, TxContext::sender(ctx), ); } - public fun create_parent_and_child(ctx: &mut TxContext) { + public(script) fun create_parent_and_child(ctx: &mut TxContext) { let parent_id = TxContext::new_id(ctx); let child = Child { id: TxContext::new_id(ctx) }; let (parent_id, child_ref) = Transfer::transfer_to_object_id(child, parent_id); @@ -41,36 +41,36 @@ module ObjectOwner::ObjectOwner { Transfer::transfer(parent, TxContext::sender(ctx)); } - public fun add_child(parent: &mut Parent, child: Child, _ctx: &mut TxContext) { + public(script) fun add_child(parent: &mut Parent, child: Child, _ctx: &mut TxContext) { let child_ref = Transfer::transfer_to_object(child, parent); Option::fill(&mut parent.child, child_ref); } // Call to mutate_child will fail if its owned by a parent, // since all owners must be in the arguments for authentication. - public fun mutate_child(_child: &mut Child, _ctx: &mut TxContext) {} + public(script) fun mutate_child(_child: &mut Child, _ctx: &mut TxContext) {} // This should always succeeds, even when child is not owned by parent. - public fun mutate_child_with_parent(_child: &mut Child, _parent: &mut Parent, _ctx: &mut TxContext) {} + public(script) fun mutate_child_with_parent(_child: &mut Child, _parent: &mut Parent, _ctx: &mut TxContext) {} - public fun transfer_child(parent: &mut Parent, child: Child, new_parent: &mut Parent, _ctx: &mut TxContext) { + public(script) fun transfer_child(parent: &mut Parent, child: Child, new_parent: &mut Parent, _ctx: &mut TxContext) { let child_ref = Option::extract(&mut parent.child); let new_child_ref = Transfer::transfer_child_to_object(child, child_ref, new_parent); Option::fill(&mut new_parent.child, new_child_ref); } - public fun remove_child(parent: &mut Parent, child: Child, ctx: &mut TxContext) { + public(script) fun remove_child(parent: &mut Parent, child: Child, ctx: &mut TxContext) { let child_ref = Option::extract(&mut parent.child); Transfer::transfer_child_to_address(child, child_ref, TxContext::sender(ctx)); } // Call to delete_child can fail if it's still owned by a parent. - public fun delete_child(child: Child, _parent: &mut Parent, _ctx: &mut TxContext) { + public(script) fun delete_child(child: Child, _parent: &mut Parent, _ctx: &mut TxContext) { let Child { id } = child; ID::delete(id); } - public fun delete_parent_and_child(parent: Parent, child: Child, _ctx: &mut TxContext) { + public(script) fun delete_parent_and_child(parent: Parent, child: Child, _ctx: &mut TxContext) { let Parent { id, child: child_ref_opt } = parent; let child_ref = Option::extract(&mut child_ref_opt); Option::destroy_none(child_ref_opt); diff --git a/sui_core/src/unit_tests/data/object_wrapping/sources/ObjectWrapping.move b/sui_core/src/unit_tests/data/object_wrapping/sources/ObjectWrapping.move index 01e1c651a4e23..74acfa16a1463 100644 --- a/sui_core/src/unit_tests/data/object_wrapping/sources/ObjectWrapping.move +++ b/sui_core/src/unit_tests/data/object_wrapping/sources/ObjectWrapping.move @@ -16,7 +16,7 @@ module ObjectWrapping::ObjectWrapping { child: Option, } - public fun create_child(ctx: &mut TxContext) { + public(script) fun create_child(ctx: &mut TxContext) { Transfer::transfer( Child { id: TxContext::new_id(ctx), @@ -25,7 +25,7 @@ module ObjectWrapping::ObjectWrapping { ) } - public fun create_parent(child: Child, ctx: &mut TxContext) { + public(script) fun create_parent(child: Child, ctx: &mut TxContext) { Transfer::transfer( Parent { id: TxContext::new_id(ctx), @@ -35,11 +35,11 @@ module ObjectWrapping::ObjectWrapping { ) } - public fun set_child(parent: &mut Parent, child: Child, _ctx: &mut TxContext) { + public(script) fun set_child(parent: &mut Parent, child: Child, _ctx: &mut TxContext) { Option::fill(&mut parent.child, child) } - public fun extract_child(parent: &mut Parent, ctx: &mut TxContext) { + public(script) fun extract_child(parent: &mut Parent, ctx: &mut TxContext) { let child = Option::extract(&mut parent.child); Transfer::transfer( child, @@ -47,7 +47,7 @@ module ObjectWrapping::ObjectWrapping { ) } - public fun delete_parent(parent: Parent, _ctx: &mut TxContext) { + public(script) fun delete_parent(parent: Parent, _ctx: &mut TxContext) { let Parent { id: parent_id, child: child_opt } = parent; ID::delete(parent_id); if (Option::is_some(&child_opt)) { diff --git a/sui_programmability/adapter/src/adapter.rs b/sui_programmability/adapter/src/adapter.rs index e82f24a0b06ec..fd9e414c70590 100644 --- a/sui_programmability/adapter/src/adapter.rs +++ b/sui_programmability/adapter/src/adapter.rs @@ -5,7 +5,9 @@ use anyhow::Result; use crate::bytecode_rewriter::ModuleHandleRewriter; use move_binary_format::{ - access::ModuleAccess, errors::PartialVMResult, file_format::CompiledModule, + access::ModuleAccess, + errors::PartialVMResult, + file_format::{CompiledModule, LocalIndex, SignatureToken, StructHandleIndex}, }; use sui_framework::EventType; use sui_types::{ @@ -16,16 +18,18 @@ use sui_types::{ gas::SuiGasStatus, id::VersionedID, messages::CallResult, - move_package::*, - object::{MoveObject, Object, Owner}, + object::{Data, MoveObject, Object, Owner}, storage::{DeleteKind, Storage}, }; -use sui_verifier::verifier; +use sui_verifier::{ + entry_points_verifier::{self, INIT_FN_NAME}, + verifier, +}; use move_core_types::{ account_address::AccountAddress, identifier::Identifier, - language_storage::{ModuleId, TypeTag}, + language_storage::{ModuleId, StructTag, TypeTag}, resolver::{ModuleResolver, ResourceResolver}, value::MoveTypeLayout, }; @@ -35,7 +39,6 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, convert::TryFrom, fmt::Debug, - sync::Arc, }; pub use move_vm_runtime::move_vm::MoveVM; @@ -44,46 +47,8 @@ pub use move_vm_runtime::move_vm::MoveVM; #[path = "unit_tests/adapter_tests.rs"] mod adapter_tests; -// This structure holds a VM and a cache of packages that contain -// in turn caches of their Function entry points, -// that involve re-computation / deserialization to otherwise get. -pub struct SuiMoveVM { - movevm: MoveVM, - - // TODO: change this to an LRU cache, to avoid running out of - // memory. - package_cache: parking_lot::RwLock>>, -} - -impl SuiMoveVM { - /// Make a new struct from a VM - pub fn new(vm: MoveVM) -> SuiMoveVM { - SuiMoveVM { - movevm: vm, - package_cache: parking_lot::RwLock::new(HashMap::new()), - } - } - - /// Returns an object from the cache if one is available - /// and otherwise caches a copy of this object. - pub fn get_package(&self, object: &Object) -> Arc { - let id = object.id(); - - if let Some(cached_object) = self.package_cache.read().get(&id) { - return cached_object.clone(); - } - - let arc_object = Arc::new(object.clone()); - self.package_cache.write().insert(id, arc_object.clone()); - arc_object - } -} - -pub fn new_move_vm(natives: NativeFunctionTable) -> Result, SuiError> { - Ok(Arc::new(SuiMoveVM { - movevm: MoveVM::new(natives).map_err(|_| SuiError::ExecutionInvariantViolation)?, - package_cache: parking_lot::RwLock::new(HashMap::new()), - })) +pub fn new_move_vm(natives: NativeFunctionTable) -> Result { + MoveVM::new(natives).map_err(|_| SuiError::ExecutionInvariantViolation) } /// Execute `module::function(object_args ++ pure_args)` as a call from `sender` with the given `gas_budget`. @@ -97,11 +62,10 @@ pub fn new_move_vm(natives: NativeFunctionTable) -> Result, SuiEr /// TODO: Do we really need the two layers? #[allow(clippy::too_many_arguments)] pub fn execute + ModuleResolver + Storage>( - vm: &SuiMoveVM, + vm: &MoveVM, state_view: &mut S, _natives: &NativeFunctionTable, - package_object: &Object, - module: &Identifier, + module_id: ModuleId, function: &Identifier, type_args: Vec, object_args: Vec, @@ -119,22 +83,14 @@ pub fn execute + ModuleResolver + ModuleResolver + Storage, >( - vm: &SuiMoveVM, + vm: &MoveVM, state_view: &mut S, module_id: &ModuleId, function: &Identifier, type_args: Vec, args: Vec>, - mutable_ref_objects: Vec, + mut mutable_ref_objects: BTreeMap, by_value_objects: BTreeMap, object_owner_map: HashMap, gas_status: &mut SuiGasStatus, // gas status for the current call operation ctx: &mut TxContext, ) -> SuiResult> { - let mut session = vm.movevm.new_session(state_view); + let mut session = vm.new_session(state_view); + // script visibility checked manually for entry points let result = session .execute_function_bypass_visibility( module_id, @@ -207,11 +164,13 @@ fn execute_internal< let updated_ctx: TxContext = bcs::from_bytes(&ctx_bytes).unwrap(); ctx.update_state(updated_ctx)?; - let mutable_refs = mutable_ref_objects.into_iter().zip( + let mutable_refs = mutable_reference_outputs .into_iter() - .map(|(_local_idx, bytes, _layout)| bytes), - ); + .map(|(local_idx, bytes, _layout)| { + let object = mutable_ref_objects.remove(&local_idx).unwrap(); + (object, bytes) + }); process_successful_execution( state_view, by_value_objects, @@ -220,6 +179,8 @@ fn execute_internal< ctx, object_owner_map, )?; + // All mutable references should have been marked as updated + debug_assert!(mutable_ref_objects.is_empty()); Ok(process_return_values(&return_values)) } // charge for all computations so far @@ -310,8 +271,6 @@ pub fn publish + ModuleResolver + ModuleResolver + Storage, >( state_view: &mut S, - vm: &SuiMoveVM, + vm: &MoveVM, modules: Vec, ctx: &mut TxContext, gas_status: &mut SuiGasStatus, ) -> SuiResult { let mut modules_to_init = Vec::new(); for module in modules.iter() { - if module_has_init(module) { + if entry_points_verifier::module_has_init(module) { modules_to_init.push(module.self_id()); } } @@ -345,11 +304,12 @@ pub fn store_package_and_init_modules< /// Modules in module_ids_to_init must have the init method defined fn init_modules + ModuleResolver + Storage>( state_view: &mut S, - vm: &SuiMoveVM, + vm: &MoveVM, module_ids_to_init: Vec, ctx: &mut TxContext, gas_status: &mut SuiGasStatus, ) -> SuiResult { + let init_ident = Identifier::new(INIT_FN_NAME.as_str()).unwrap(); for module_id in module_ids_to_init { let args = vec![ctx.to_vec()]; @@ -357,10 +317,10 @@ fn init_modules + ModuleResolver>, + pub by_value_objects: BTreeMap, + pub mutable_ref_objects: BTreeMap, +} + +/// - Check that `package_object`, `module` and `function` are valid +/// - Check that the the signature of `function` is well-typed w.r.t `type_args`, `object_args`, and `pure_args` +/// - Return the ID of the resolved module, a vector of BCS encoded arguments to pass to the VM, and a partitioning +/// of the input objects into objects passed by value vs by mutable reference +pub fn resolve_and_type_check( + module: &CompiledModule, + function: &Identifier, + type_args: &[TypeTag], + object_args: Vec, + mut pure_args: Vec>, +) -> Result { + // Resolve the function we are calling + let function_str = function.as_ident_str(); + let module_id = module.self_id(); + let fdef_opt = module.function_defs.iter().find(|fdef| { + module.identifier_at(module.function_handle_at(fdef.function).name) == function_str + }); + let fdef = match fdef_opt { + Some(fdef) => fdef, + None => { + return Err(SuiError::FunctionNotFound { + error: format!( + "Could not resolve function '{}' in module {}", + function, &module_id, + ), + }) + } + }; + let fhandle = module.function_handle_at(fdef.function); + + // check arity of type and value arguments + if fhandle.type_parameters.len() != type_args.len() { + return Err(SuiError::InvalidFunctionSignature { + error: format!( + "Expected {:?} type arguments, but found {:?}", + fhandle.type_parameters.len(), + type_args.len() + ), + }); + } + + // total number of args is |objects| + |pure_args| + 1 for the the `TxContext` object + let num_args = object_args.len() + pure_args.len() + 1; + let parameters = &module.signature_at(fhandle.parameters).0; + if parameters.len() != num_args { + return Err(SuiError::InvalidFunctionSignature { + error: format!( + "Expected {:?} arguments calling function '{}', but found {:?}", + parameters.len(), + function, + num_args + ), + }); + } + + entry_points_verifier::verify_entry_function(module, fdef, type_args)?; + + // type check object arguments passed in by value and by reference + let mut args = Vec::new(); + let mut mutable_ref_objects = BTreeMap::new(); + let mut by_value_objects = BTreeMap::new(); + #[cfg(debug_assertions)] + let mut num_immutable_objects = 0; + #[cfg(debug_assertions)] + let num_objects = object_args.len(); + + for (idx, object) in object_args.into_iter().enumerate() { + let param_type = ¶meters[idx]; + let move_object = match &object.data { + Data::Move(m) => m, + Data::Package(_) => { + let error = format!( + "Found module argument, but function expects {:?}", + param_type + ); + return Err(SuiError::TypeError { error }); + } + }; + args.push(move_object.contents().to_vec()); + // check that m.type_ matches the parameter types of the function + let inner_param_type = match ¶m_type { + SignatureToken::MutableReference(inner_t) => { + if object.is_read_only() { + let error = format!( + "Argument {} is expected to be mutable, immutable object found", + idx + ); + return Err(SuiError::TypeError { error }); + } + &**inner_t + } + SignatureToken::Reference(inner_t) => { + #[cfg(debug_assertions)] + { + num_immutable_objects += 1; + } + &**inner_t + } + t @ SignatureToken::Struct(_) + | t @ SignatureToken::StructInstantiation(_, _) + | t @ SignatureToken::TypeParameter(_) => { + if object.is_shared() { + // Forbid passing shared (both mutable and immutable) object by value. + // This ensures that shared object cannot be transferred, deleted or wrapped. + return Err(SuiError::TypeError { + error: format!( + "Shared object cannot be passed by-value, found in argument {}", + idx + ), + }); + } + t + } + t => { + return Err(SuiError::TypeError { + error: format!( + "Found object argument {}, but function expects {:?}", + move_object.type_, t + ), + }) + } + }; + type_check_struct(module, type_args, &move_object.type_, inner_param_type)?; + match ¶m_type { + SignatureToken::MutableReference(_) => { + let _prev = mutable_ref_objects.insert(idx as LocalIndex, object); + debug_assert!(_prev.is_none()); + } + SignatureToken::Reference(_) => (), + _ => { + let _prev = by_value_objects.insert(object.id(), object); + // should always pass due to earlier "no duplicate ID's" check + debug_assert!(_prev.is_none()); + } + } + } + + debug_assert!( + by_value_objects.len() + mutable_ref_objects.len() + num_immutable_objects == num_objects + ); + // verify_entry_function ensures that pure_args are all primitives + args.append(&mut pure_args); + + Ok(TypeCheckSuccess { + module_id, + args, + by_value_objects, + mutable_ref_objects, + }) +} + +fn type_check_struct( + module: &CompiledModule, + function_type_arguments: &[TypeTag], + arg_type: &StructTag, + param_type: &SignatureToken, +) -> Result<(), SuiError> { + if !struct_tag_equals_sig_token(module, function_type_arguments, arg_type, param_type) { + Err(SuiError::TypeError { + error: format!( + "Expected argument of type {}, but found type {}", + sui_verifier::format_signature_token(module, param_type), + arg_type + ), + }) + } else { + Ok(()) + } +} + +fn type_tag_equals_sig_token( + module: &CompiledModule, + function_type_arguments: &[TypeTag], + arg_type: &TypeTag, + param_type: &SignatureToken, +) -> bool { + match (arg_type, param_type) { + (TypeTag::Bool, SignatureToken::Bool) + | (TypeTag::U8, SignatureToken::U8) + | (TypeTag::U64, SignatureToken::U64) + | (TypeTag::U128, SignatureToken::U128) + | (TypeTag::Address, SignatureToken::Address) + | (TypeTag::Signer, SignatureToken::Signer) => true, + + (TypeTag::Vector(inner_arg_type), SignatureToken::Vector(inner_param_type)) => { + type_tag_equals_sig_token( + module, + function_type_arguments, + inner_arg_type, + inner_param_type, + ) + } + + (TypeTag::Struct(arg_struct), SignatureToken::Struct(_)) + | (TypeTag::Struct(arg_struct), SignatureToken::StructInstantiation(_, _)) => { + struct_tag_equals_sig_token(module, function_type_arguments, arg_struct, param_type) + } + + (_, SignatureToken::TypeParameter(idx)) => { + arg_type == &function_type_arguments[*idx as usize] + } + _ => false, + } +} + +fn struct_tag_equals_sig_token( + module: &CompiledModule, + function_type_arguments: &[TypeTag], + arg_type: &StructTag, + param_type: &SignatureToken, +) -> bool { + match param_type { + SignatureToken::Struct(idx) => { + struct_tag_equals_struct_inst(module, function_type_arguments, arg_type, *idx, &[]) + } + SignatureToken::StructInstantiation(idx, args) => { + struct_tag_equals_struct_inst(module, function_type_arguments, arg_type, *idx, args) + } + _ => false, + } +} + +fn struct_tag_equals_struct_inst( + module: &CompiledModule, + function_type_arguments: &[TypeTag], + arg_type: &StructTag, + param_type: StructHandleIndex, + param_type_arguments: &[SignatureToken], +) -> bool { + let (address, module_name, struct_name) = sui_verifier::resolve_struct(module, param_type); + + // same address + &arg_type.address == address + // same module + && arg_type.module.as_ident_str() == module_name + // same struct name + && arg_type.name.as_ident_str() == struct_name + // same type parameters + && arg_type.type_params.len() == param_type_arguments.len() + && arg_type.type_params.iter().zip(param_type_arguments).all( + |(arg_type_arg, param_type_arg)| { + type_tag_equals_sig_token( + module, + function_type_arguments, + arg_type_arg, + param_type_arg, + ) + }, + ) +} diff --git a/sui_programmability/adapter/src/unit_tests/adapter_tests.rs b/sui_programmability/adapter/src/unit_tests/adapter_tests.rs index 335f053bc89bc..cf8e608bc91e2 100644 --- a/sui_programmability/adapter/src/unit_tests/adapter_tests.rs +++ b/sui_programmability/adapter/src/unit_tests/adapter_tests.rs @@ -159,11 +159,13 @@ impl ModuleResolver for InMemoryStorage { fn get_module(&self, module_id: &ModuleId) -> Result>, Self::Error> { Ok(self .read_object(&ObjectID::from(*module_id.address())) - .map(|o| match &o.data { - Data::Package(m) => m.serialized_module_map()[module_id.name().as_str()] - .clone() - .into_vec(), - Data::Move(_) => panic!("Type error"), + .and_then(|o| match &o.data { + Data::Package(m) => Some( + m.serialized_module_map()[module_id.name().as_str()] + .clone() + .into_vec(), + ), + Data::Move(_) => None, })) } } @@ -191,15 +193,14 @@ fn call( object_args: Vec, pure_args: Vec>, ) -> SuiResult> { - let package = storage.find_package(module_name).unwrap(); - let vm = adapter::new_move_vm(native_functions.clone()).expect("No errors"); + let package = storage.find_package(module_name).unwrap(); + let module_id = ModuleId::new(package.id().into(), Identifier::new(module_name).unwrap()); adapter::execute( &vm, storage, native_functions, - &package, - &Identifier::new(module_name).unwrap(), + module_id, &Identifier::new(fun_name).unwrap(), type_args, object_args, @@ -628,12 +629,15 @@ fn test_move_call_incorrect_function() { // Instead of calling on the genesis package, we are calling the gas object. let vm = adapter::new_move_vm(native_functions.clone()).expect("No errors"); + let module_id = ModuleId::new( + gas_object.id().into(), + Identifier::new("ObjectBasics").unwrap(), + ); let status = adapter::execute( &vm, &mut storage, &native_functions, - &gas_object, - &Identifier::new("ObjectBasics").unwrap(), + module_id, &Identifier::new("create").unwrap(), vec![], vec![], @@ -642,9 +646,9 @@ fn test_move_call_incorrect_function() { &mut TxContext::random_for_testing_only(), ); let err = status.unwrap_err(); - assert!(err - .to_string() - .contains("Expected a module object, but found a Move object")); + // TODO redo these contains messages to matches? need better error statuses + // LINKER_ERROR is a verification error by the VM given since this module is not found + assert!(err.to_string().contains("LINKER_ERROR")); // Calling a non-existing function. let pure_args = vec![10u64.to_le_bytes().to_vec()]; diff --git a/sui_programmability/adapter/src/unit_tests/data/call_ret/sources/M1.move b/sui_programmability/adapter/src/unit_tests/data/call_ret/sources/M1.move index 73ece7a5b6cff..15efb8af8d5bd 100644 --- a/sui_programmability/adapter/src/unit_tests/data/call_ret/sources/M1.move +++ b/sui_programmability/adapter/src/unit_tests/data/call_ret/sources/M1.move @@ -8,26 +8,26 @@ module Test::M1 { const ADDR: address = @0x42; - public fun get_u64(_ctx: &mut TxContext): u64 { + public(script) fun get_u64(_ctx: &mut TxContext): u64 { 42 } - public fun get_addr(_ctx: &mut TxContext): address { + public(script) fun get_addr(_ctx: &mut TxContext): address { ADDR } - public fun get_tuple(_ctx: &mut TxContext): (u64, address) { + public(script) fun get_tuple(_ctx: &mut TxContext): (u64, address) { (42, ADDR) } - public fun get_vec(_ctx: &mut TxContext): vector { + public(script) fun get_vec(_ctx: &mut TxContext): vector { let vec = Vector::empty(); Vector::push_back(&mut vec, 42); Vector::push_back(&mut vec, 7); vec } - public fun get_vec_vec(_ctx: &mut TxContext): vector> { + public(script) fun get_vec_vec(_ctx: &mut TxContext): vector> { let vec = Vector::empty(); Vector::push_back(&mut vec, 42); Vector::push_back(&mut vec, 7); diff --git a/sui_programmability/adapter/src/unit_tests/data/publish_init_public/sources/M1.move b/sui_programmability/adapter/src/unit_tests/data/publish_init_public/sources/M1.move index 810c0ec41bede..3f853c21bc24c 100644 --- a/sui_programmability/adapter/src/unit_tests/data/publish_init_public/sources/M1.move +++ b/sui_programmability/adapter/src/unit_tests/data/publish_init_public/sources/M1.move @@ -12,7 +12,7 @@ module Test::M1 { } // public initializer - should not be executed - public fun init(ctx: &mut TxContext) { + public(script) fun init(ctx: &mut TxContext) { let value = 42; let singleton = Object { id: TxContext::new_id(ctx), value }; Transfer::transfer(singleton, TxContext::sender(ctx)) diff --git a/sui_programmability/adapter/src/unit_tests/data/simple_call/sources/M1.move b/sui_programmability/adapter/src/unit_tests/data/simple_call/sources/M1.move index a4072a9e0159c..3693a97b49c3d 100644 --- a/sui_programmability/adapter/src/unit_tests/data/simple_call/sources/M1.move +++ b/sui_programmability/adapter/src/unit_tests/data/simple_call/sources/M1.move @@ -16,7 +16,7 @@ module Test::M1 { value1 } - public fun create(value: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun create(value: u64, recipient: address, ctx: &mut TxContext) { Transfer::transfer( Object { id: TxContext::new_id(ctx), value }, recipient diff --git a/sui_programmability/examples/basics/sources/Lock.move b/sui_programmability/examples/basics/sources/Lock.move index 25a604781e418..d51d2fc60c7ad 100644 --- a/sui_programmability/examples/basics/sources/Lock.move +++ b/sui_programmability/examples/basics/sources/Lock.move @@ -41,7 +41,7 @@ module Basics::Lock { /// Lock some content inside a shared object. A Key is created and is /// sent to the transaction sender. - public fun create(obj: T, ctx: &mut TxContext) { + public(script) fun create(obj: T, ctx: &mut TxContext) { let id = TxContext::new_id(ctx); let for = *ID::inner(&id); @@ -58,7 +58,7 @@ module Basics::Lock { /// Lock something inside a shared object using a Key. Aborts if /// lock is not empty or if key doesn't match the lock. - public fun lock( + public(script) fun lock( obj: T, lock: &mut Lock, key: &Key, @@ -108,7 +108,7 @@ module Basics::LockTest { } #[test] - fun test_lock() { + public(script) fun test_lock() { let user1 = @0x1; let user2 = @0x2; diff --git a/sui_programmability/examples/basics/sources/Object.move b/sui_programmability/examples/basics/sources/Object.move index ed20e3ac772cf..28c7f7f8123d4 100644 --- a/sui_programmability/examples/basics/sources/Object.move +++ b/sui_programmability/examples/basics/sources/Object.move @@ -85,7 +85,7 @@ module Basics::Object { /// input objects + created objects + emitted events, increments the /// sequence number each object, creates a hash that commits to the /// outputs, etc. - public fun main( + public(script) fun main( to_read: &Object, to_write: &mut Object, to_consume: Object, diff --git a/sui_programmability/examples/basics/sources/Sandwich.move b/sui_programmability/examples/basics/sources/Sandwich.move index 88252bf0aed5f..6e59470a1a906 100644 --- a/sui_programmability/examples/basics/sources/Sandwich.move +++ b/sui_programmability/examples/basics/sources/Sandwich.move @@ -33,21 +33,21 @@ module Basics::Sandwich { const EINSUFFICIENT_FUNDS: u64 = 0; /// Exchange `c` for some ham - public fun buy_ham(c: Coin, ctx: &mut TxContext): Ham { + fun buy_ham(c: Coin, ctx: &mut TxContext): Ham { assert!(Coin::value(&c) == HAM_PRICE, EINSUFFICIENT_FUNDS); Transfer::transfer(c, GROCERY); Ham { id: TxContext::new_id(ctx) } } /// Exchange `c` for some bread - public fun buy_bread(c: Coin, ctx: &mut TxContext): Bread { + fun buy_bread(c: Coin, ctx: &mut TxContext): Bread { assert!(Coin::value(&c) == BREAD_PRICE, EINSUFFICIENT_FUNDS); Transfer::transfer(c, GROCERY); Bread { id: TxContext::new_id(ctx) } } /// Combine the `ham` and `bread` into a delicious sandwich - public fun make_sandwich( + public(script) fun make_sandwich( ham: Ham, bread: Bread, ctx: &mut TxContext ) { let Ham { id: ham_id } = ham; diff --git a/sui_programmability/examples/defi/sources/Escrow.move b/sui_programmability/examples/defi/sources/Escrow.move index f4c3fb1cca563..d4b8064c5f788 100644 --- a/sui_programmability/examples/defi/sources/Escrow.move +++ b/sui_programmability/examples/defi/sources/Escrow.move @@ -51,7 +51,7 @@ module DeFi::Escrow { } /// Trusted third party can swap compatible objects - public fun swap( + public(script) fun swap( obj1: EscrowedObj, obj2: EscrowedObj, _ctx: &mut TxContext @@ -84,7 +84,7 @@ module DeFi::Escrow { } /// Trusted third party can always return an escrowed object to its original owner - public fun return_to_sender( + public(script) fun return_to_sender( obj: EscrowedObj, _ctx: &mut TxContext ) { diff --git a/sui_programmability/examples/defi/sources/FlashLender.move b/sui_programmability/examples/defi/sources/FlashLender.move index 4abe97c49a5a3..a8254081c3a13 100644 --- a/sui_programmability/examples/defi/sources/FlashLender.move +++ b/sui_programmability/examples/defi/sources/FlashLender.move @@ -77,7 +77,7 @@ module DeFi::FlashLender { } /// Same as `new`, but transfer `WithdrawCap` to the transaction sender - public fun create(to_lend: Coin, fee: u64, ctx: &mut TxContext) { + public(script) fun create(to_lend: Coin, fee: u64, ctx: &mut TxContext) { let withdraw_cap = new(to_lend, fee, ctx); Transfer::transfer(withdraw_cap, TxContext::sender(ctx)) } @@ -128,7 +128,7 @@ module DeFi::FlashLender { } /// Allow admin to add more funds to `self` - public fun deposit( + public(script) fun deposit( self: &mut FlashLender, admin_cap: &AdminCap, coin: Coin, _ctx: &mut TxContext ) { // only the holder of the `AdminCap` for `self` can deposit funds @@ -138,7 +138,7 @@ module DeFi::FlashLender { } /// Allow admin to update the fee for `self` - public fun update_fee( + public(script) fun update_fee( self: &mut FlashLender, admin_cap: &AdminCap, new_fee: u64, _ctx: &mut TxContext ) { // only the holder of the `AdminCap` for `self` can update the fee diff --git a/sui_programmability/examples/defi/sources/SharedEscrow.move b/sui_programmability/examples/defi/sources/SharedEscrow.move index 8669717e2746c..16115b0267349 100644 --- a/sui_programmability/examples/defi/sources/SharedEscrow.move +++ b/sui_programmability/examples/defi/sources/SharedEscrow.move @@ -50,7 +50,7 @@ module DeFi::SharedEscrow { } /// The `recipient` of the escrow can exchange `obj` with the escrowed item - public fun exchange( + public(script) fun exchange( obj: ExchangeForT, escrow: &mut EscrowedObj, ctx: &mut TxContext @@ -65,7 +65,7 @@ module DeFi::SharedEscrow { } /// The `creator` can cancel the escrow and get back the escrowed item - public fun cancel( + public(script) fun cancel( escrow: &mut EscrowedObj, ctx: &mut TxContext ) { diff --git a/sui_programmability/examples/defi/tests/EscrowTests.move b/sui_programmability/examples/defi/tests/EscrowTests.move index 16b84f484cf0f..e352a4cae3f50 100644 --- a/sui_programmability/examples/defi/tests/EscrowTests.move +++ b/sui_programmability/examples/defi/tests/EscrowTests.move @@ -29,7 +29,7 @@ module DeFi::EscrowTests { } #[test] - public fun test_escrow_flow() { + public(script) fun test_escrow_flow() { // Both Alice and Bob send items to the third party let scenario = &mut send_to_escrow(ALICE_ADDRESS, BOB_ADDRESS); swap(scenario, &THIRD_PARTY_ADDRESS); @@ -40,7 +40,7 @@ module DeFi::EscrowTests { } #[test] - public fun test_return_to_sender() { + public(script) fun test_return_to_sender() { // Both Alice and Bob send items to the third party let scenario = &mut send_to_escrow(ALICE_ADDRESS, BOB_ADDRESS); @@ -63,7 +63,7 @@ module DeFi::EscrowTests { #[test] #[expected_failure(abort_code = 1)] - public fun test_swap_wrong_objects() { + public(script) fun test_swap_wrong_objects() { // Both Alice and Bob send items to the third party except that Alice wants to exchange // for a different object than Bob's let scenario = &mut send_to_escrow_with_overrides(ALICE_ADDRESS, BOB_ADDRESS, true, false); @@ -72,14 +72,14 @@ module DeFi::EscrowTests { #[test] #[expected_failure(abort_code = 0)] - public fun test_swap_wrong_recipient() { + public(script) fun test_swap_wrong_recipient() { // Both Alice and Bob send items to the third party except that Alice put a different // recipient than Bob let scenario = &mut send_to_escrow_with_overrides(ALICE_ADDRESS, BOB_ADDRESS, false, true); swap(scenario, &THIRD_PARTY_ADDRESS); } - fun swap(scenario: &mut Scenario, third_party: &address) { + public(script) fun swap(scenario: &mut Scenario, third_party: &address) { TestScenario::next_tx(scenario, third_party); { let item_a = TestScenario::take_object>(scenario); diff --git a/sui_programmability/examples/defi/tests/FlashLenderTests.move b/sui_programmability/examples/defi/tests/FlashLenderTests.move index 6b34276eb784c..6d899f2547d90 100644 --- a/sui_programmability/examples/defi/tests/FlashLenderTests.move +++ b/sui_programmability/examples/defi/tests/FlashLenderTests.move @@ -9,7 +9,7 @@ module DeFi::FlashLenderTests { use Sui::TestScenario; #[test] - fun flash_loan_example() { + public(script) fun flash_loan_example() { let admin = @0x1; let borrower = @0x2; diff --git a/sui_programmability/examples/defi/tests/SharedEscrowTest.move b/sui_programmability/examples/defi/tests/SharedEscrowTest.move index 62f1f2b4db85a..79ff5fa4f5599 100644 --- a/sui_programmability/examples/defi/tests/SharedEscrowTest.move +++ b/sui_programmability/examples/defi/tests/SharedEscrowTest.move @@ -29,7 +29,7 @@ module DeFi::SharedEscrowTests { } #[test] - public fun test_escrow_flow() { + public(script) fun test_escrow_flow() { // Alice creates the escrow let (scenario, item_b_versioned_id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); @@ -42,7 +42,7 @@ module DeFi::SharedEscrowTests { } #[test] - public fun test_cancel() { + public(script) fun test_cancel() { // Alice creates the escrow let (scenario, id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); ID::delete(id); @@ -59,7 +59,7 @@ module DeFi::SharedEscrowTests { #[test] #[expected_failure(abort_code = 0)] - public fun test_cancel_with_wrong_owner() { + public(script) fun test_cancel_with_wrong_owner() { // Alice creates the escrow let (scenario, id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); ID::delete(id); @@ -71,7 +71,7 @@ module DeFi::SharedEscrowTests { #[test] #[expected_failure(abort_code = 2)] - public fun test_swap_wrong_objects() { + public(script) fun test_swap_wrong_objects() { // Alice creates the escrow in exchange for item b let (scenario, item_b_versioned_id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); ID::delete(item_b_versioned_id); @@ -86,7 +86,7 @@ module DeFi::SharedEscrowTests { #[test] #[expected_failure(abort_code = 1)] - public fun test_swap_wrong_recipient() { + public(script) fun test_swap_wrong_recipient() { // Alice creates the escrow in exchange for item b let (scenario, item_b_versioned_id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); let scenario = &mut scenario; @@ -97,7 +97,7 @@ module DeFi::SharedEscrowTests { #[test] #[expected_failure(abort_code = 3)] - public fun test_cancel_twice() { + public(script) fun test_cancel_twice() { // Alice creates the escrow let (scenario, id) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); ID::delete(id); @@ -115,7 +115,7 @@ module DeFi::SharedEscrowTests { cancel(scenario, &ALICE_ADDRESS); } - fun cancel(scenario: &mut Scenario, initiator: &address) { + public(script) fun cancel(scenario: &mut Scenario, initiator: &address) { TestScenario::next_tx(scenario, initiator); { let escrow = TestScenario::take_object>(scenario); @@ -125,7 +125,7 @@ module DeFi::SharedEscrowTests { }; } - fun exchange(scenario: &mut Scenario, bob: &address, item_b_verioned_id: VersionedID) { + public(script) fun exchange(scenario: &mut Scenario, bob: &address, item_b_verioned_id: VersionedID) { TestScenario::next_tx(scenario, bob); { let escrow = TestScenario::take_object>(scenario); diff --git a/sui_programmability/examples/fungible_tokens/sources/BASKET.move b/sui_programmability/examples/fungible_tokens/sources/BASKET.move index 3be94b4e83081..c5fe5b070b2ca 100644 --- a/sui_programmability/examples/fungible_tokens/sources/BASKET.move +++ b/sui_programmability/examples/fungible_tokens/sources/BASKET.move @@ -89,4 +89,4 @@ module FungibleTokens::BASKET { public fun init_for_testing(ctx: &mut TxContext) { init(ctx) } -} \ No newline at end of file +} diff --git a/sui_programmability/examples/fungible_tokens/sources/MANAGED.move b/sui_programmability/examples/fungible_tokens/sources/MANAGED.move index 45239d39251b0..570ebc2bacfe0 100644 --- a/sui_programmability/examples/fungible_tokens/sources/MANAGED.move +++ b/sui_programmability/examples/fungible_tokens/sources/MANAGED.move @@ -28,12 +28,12 @@ module FungibleTokens::MANAGED { } /// Manager can burn coins - public fun burn(treasury_cap: &mut TreasuryCap, coin: Coin, _ctx: &mut TxContext) { + public(script) fun burn(treasury_cap: &mut TreasuryCap, coin: Coin, _ctx: &mut TxContext) { Coin::burn(coin, treasury_cap) } /// Manager can transfer the treasury capability to a new manager - public fun transfer_cap(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer_cap(treasury_cap: TreasuryCap, recipient: address, _ctx: &mut TxContext) { Coin::transfer_cap(treasury_cap, recipient); } diff --git a/sui_programmability/examples/games/hero/sources/Hero.move b/sui_programmability/examples/games/hero/sources/Hero.move index 0a5ac83c02057..af23175dbd362 100644 --- a/sui_programmability/examples/games/hero/sources/Hero.move +++ b/sui_programmability/examples/games/hero/sources/Hero.move @@ -114,7 +114,7 @@ module HeroGame::Hero { /// Slay the `boar` with the `hero`'s sword, get experience. /// Aborts if the hero has 0 HP or is not strong enough to slay the boar - public fun slay(hero: &mut Hero, boar: Boar, ctx: &mut TxContext) { + public(script) fun slay(hero: &mut Hero, boar: Boar, ctx: &mut TxContext) { let Boar { id: boar_id, strength: boar_strength, hp } = boar; let hero_strength = hero_strength(hero); let boar_hp = hp; @@ -222,7 +222,7 @@ module HeroGame::Hero { } } - public fun acquire_hero(payment: Coin, ctx: &mut TxContext) { + public(script) fun acquire_hero(payment: Coin, ctx: &mut TxContext) { let sword = create_sword(payment, ctx); let hero = create_hero(sword, ctx); Transfer::transfer(hero, TxContext::sender(ctx)) @@ -240,7 +240,7 @@ module HeroGame::Hero { } /// Admin can create a potion with the given `potency` for `recipient` - public fun send_potion( + public(script) fun send_potion( potency: u64, player: address, admin: &mut GameAdmin, @@ -255,7 +255,7 @@ module HeroGame::Hero { } /// Admin can create a boar with the given attributes for `recipient` - public fun send_boar( + public(script) fun send_boar( admin: &mut GameAdmin, hp: u64, strength: u64, diff --git a/sui_programmability/examples/games/hero/sources/SeaHero.move b/sui_programmability/examples/games/hero/sources/SeaHero.move index 9da410e7b689a..c297ec8ecab1e 100644 --- a/sui_programmability/examples/games/hero/sources/SeaHero.move +++ b/sui_programmability/examples/games/hero/sources/SeaHero.move @@ -92,7 +92,7 @@ module HeroGame::SeaHero { /// Game admin can reate a monster wrapping a coin worth `reward` and send /// it to `recipient` - public fun create_monster( + public(script) fun create_monster( admin: &mut SeaHeroAdmin, reward_amount: u64, recipient: address, diff --git a/sui_programmability/examples/games/hero/sources/SeaHeroHelper.move b/sui_programmability/examples/games/hero/sources/SeaHeroHelper.move index ca107ca69a471..0860eb95978a3 100644 --- a/sui_programmability/examples/games/hero/sources/SeaHeroHelper.move +++ b/sui_programmability/examples/games/hero/sources/SeaHeroHelper.move @@ -61,7 +61,7 @@ module HeroGame::SeaHeroHelper { /// Helper should call this if they are willing to help out and slay the /// monster. - public fun slay( + public(script) fun slay( hero: &Hero, wrapper: HelpMeSlayThisMonster, ctx: &mut TxContext, ): Coin { let HelpMeSlayThisMonster { diff --git a/sui_programmability/examples/games/sources/RockPaperScissors.move b/sui_programmability/examples/games/sources/RockPaperScissors.move index dc933264c7591..9c1168def3e2f 100644 --- a/sui_programmability/examples/games/sources/RockPaperScissors.move +++ b/sui_programmability/examples/games/sources/RockPaperScissors.move @@ -117,7 +117,7 @@ module Games::RockPaperScissors { /// is initiated with default/empty values which will be filled later in the game. /// /// todo: extend with generics + T as prize - public fun new_game(player_one: address, player_two: address, ctx: &mut TxContext) { + public(script) fun new_game(player_one: address, player_two: address, ctx: &mut TxContext) { Transfer::transfer(Game { id: TxContext::new_id(ctx), prize: ThePrize { id: TxContext::new_id(ctx) }, @@ -134,7 +134,7 @@ module Games::RockPaperScissors { /// is encoded inside the [`hash`] argument. /// /// Currently there's no check on whether the game exists. - public fun player_turn(at: address, hash: vector, ctx: &mut TxContext) { + public(script) fun player_turn(at: address, hash: vector, ctx: &mut TxContext) { Transfer::transfer(PlayerTurn { hash, id: TxContext::new_id(ctx), @@ -144,7 +144,7 @@ module Games::RockPaperScissors { /// Add a hashed gesture to the game. Store it as a `hash_one` or `hash_two` depending /// on the player number (one or two) - public fun add_hash(game: &mut Game, cap: PlayerTurn, _ctx: &mut TxContext) { + public(script) fun add_hash(game: &mut Game, cap: PlayerTurn, _ctx: &mut TxContext) { let PlayerTurn { hash, id, player } = cap; let status = status(game); @@ -164,7 +164,7 @@ module Games::RockPaperScissors { /// Submit a [`Secret`] to the game owner who then matches the hash and saves the /// gesture in the [`Game`] object. - public fun reveal(at: address, salt: vector, ctx: &mut TxContext) { + public(script) fun reveal(at: address, salt: vector, ctx: &mut TxContext) { Transfer::transfer(Secret { id: TxContext::new_id(ctx), salt, @@ -175,7 +175,7 @@ module Games::RockPaperScissors { /// Use submitted [`Secret`]'s salt to find the gesture played by the player and set it /// in the [`Game`] object. /// TODO: think of ways to - public fun match_secret(game: &mut Game, secret: Secret, _ctx: &mut TxContext) { + public(script) fun match_secret(game: &mut Game, secret: Secret, _ctx: &mut TxContext) { let Secret { salt, player, id } = secret; assert!(player == game.player_one || player == game.player_two, 0); @@ -191,7 +191,7 @@ module Games::RockPaperScissors { /// The final accord to the game logic. After both secrets have been revealed, /// the game owner can choose a winner and release the prize. - public fun select_winner(game: Game, ctx: &mut TxContext) { + public(script) fun select_winner(game: Game, ctx: &mut TxContext) { assert!(status(&game) == STATUS_REVEALED, 0); let Game { diff --git a/sui_programmability/examples/games/sources/SharedTicTacToe.move b/sui_programmability/examples/games/sources/SharedTicTacToe.move index 816f58dc11e6f..b87dd5bc74d37 100644 --- a/sui_programmability/examples/games/sources/SharedTicTacToe.move +++ b/sui_programmability/examples/games/sources/SharedTicTacToe.move @@ -62,7 +62,7 @@ module Games::SharedTicTacToe { } /// `x_address` and `o_address` are the account address of the two players. - public fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { + public(script) fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { // TODO: Validate sender address, only GameAdmin can create games. let id = TxContext::new_id(ctx); @@ -83,7 +83,7 @@ module Games::SharedTicTacToe { Transfer::share_object(game); } - public fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) { + public(script) fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) { assert!(row < 3 && col < 3, EINVALID_LOCATION); assert!(game.game_status == IN_PROGRESS, EGAME_ENDED); let addr = get_cur_turn_address(game); @@ -107,12 +107,12 @@ module Games::SharedTicTacToe { } } - public fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { + public(script) fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { let TicTacToe { id, gameboard: _, cur_turn: _, game_status: _, x_address: _, o_address: _ } = game; ID::delete(id); } - public fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { + public(script) fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { let Trophy { id } = trophy; ID::delete(id); } diff --git a/sui_programmability/examples/games/sources/TicTacToe.move b/sui_programmability/examples/games/sources/TicTacToe.move index 68d84aaf249f2..693489ef192d0 100644 --- a/sui_programmability/examples/games/sources/TicTacToe.move +++ b/sui_programmability/examples/games/sources/TicTacToe.move @@ -76,7 +76,7 @@ module Games::TicTacToe { } /// `x_address` and `o_address` are the account address of the two players. - public fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { + public(script) fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { // TODO: Validate sender address, only GameAdmin can create games. let id = TxContext::new_id(ctx); @@ -111,7 +111,13 @@ module Games::TicTacToe { /// Generate a new mark intended for location (row, col). /// This new mark is not yet placed, just transferred to the game. - public fun send_mark_to_game(cap: &mut MarkMintCap, game_address: address, row: u64, col: u64, ctx: &mut TxContext) { + public(script) fun send_mark_to_game( + cap: &mut MarkMintCap, + game_address: address, + row: u64, + col: u64, + ctx: &mut TxContext, + ) { if (row > 2 || col > 2) { abort INVALID_LOCATION }; @@ -125,7 +131,7 @@ module Games::TicTacToe { Transfer::transfer(mark, game_address); } - public fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext) { + public(script) fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext) { // If we are placing the mark at the wrong turn, or if game has ended, // destroy the mark. let addr = get_cur_turn_address(game); @@ -155,7 +161,7 @@ module Games::TicTacToe { } } - public fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { + public(script) fun delete_game(game: TicTacToe, _ctx: &mut TxContext) { let TicTacToe { id, gameboard, cur_turn: _, game_status: _, x_address: _, o_address: _ } = game; while (Vector::length(&gameboard) > 0) { let row = Vector::pop_back(&mut gameboard); @@ -173,12 +179,12 @@ module Games::TicTacToe { ID::delete(id); } - public fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { + public(script) fun delete_trophy(trophy: Trophy, _ctx: &mut TxContext) { let Trophy { id } = trophy; ID::delete(id); } - public fun delete_cap(cap: MarkMintCap, _ctx: &mut TxContext) { + public(script) fun delete_cap(cap: MarkMintCap, _ctx: &mut TxContext) { let MarkMintCap { id, game_id: _, remaining_supply: _ } = cap; ID::delete(id); } diff --git a/sui_programmability/examples/games/tests/RockPaperScissorsTests.move b/sui_programmability/examples/games/tests/RockPaperScissorsTests.move index 8ea82bdccf911..8ea0e7c530012 100644 --- a/sui_programmability/examples/games/tests/RockPaperScissorsTests.move +++ b/sui_programmability/examples/games/tests/RockPaperScissorsTests.move @@ -9,7 +9,7 @@ module Games::RockPaperScissorsTests { use Std::Hash; #[test] - public fun play_rock_paper_scissors() { + public(script) fun play_rock_paper_scissors() { // So these are our heroes. let the_main_guy = @0xA1C05; let mr_lizard = @0xA55555; diff --git a/sui_programmability/examples/games/tests/SharedTicTacToeTests.move b/sui_programmability/examples/games/tests/SharedTicTacToeTests.move index 9ebfd30532a1b..b81a540c28091 100644 --- a/sui_programmability/examples/games/tests/SharedTicTacToeTests.move +++ b/sui_programmability/examples/games/tests/SharedTicTacToeTests.move @@ -14,7 +14,7 @@ module Games::SharedTicTacToeTests { const DRAW: u8 = 3; #[test] - fun play_tictactoe() { + public(script) fun play_tictactoe() { let player_x = @0x0; let player_o = @0x1; @@ -81,7 +81,7 @@ module Games::SharedTicTacToeTests { #[test] - fun play_tictactoe_draw() { + public(script) fun play_tictactoe_draw() { let player_x = @0x0; let player_o = @0x1; @@ -188,7 +188,7 @@ module Games::SharedTicTacToeTests { } - fun place_mark( + public(script) fun place_mark( row: u8, col: u8, player: &address, diff --git a/sui_programmability/examples/games/tests/TicTacToeTests.move b/sui_programmability/examples/games/tests/TicTacToeTests.move index 5bdbd4857a04d..86c8dd26420f6 100644 --- a/sui_programmability/examples/games/tests/TicTacToeTests.move +++ b/sui_programmability/examples/games/tests/TicTacToeTests.move @@ -15,7 +15,7 @@ module Games::TicTacToeTests { #[test] - fun play_tictactoe() { + public(script) fun play_tictactoe() { let admin = @0x0; let player_x = @0x1; let player_o = @0x2; @@ -84,7 +84,7 @@ module Games::TicTacToeTests { #[test] - fun play_tictactoe_draw() { + public(script) fun play_tictactoe_draw() { let admin = @0x0; let player_x = @0x1; let player_o = @0x2; @@ -191,7 +191,7 @@ module Games::TicTacToeTests { assert!(!TestScenario::can_take_object(scenario), 1); } - fun place_mark( + public(script) fun place_mark( row: u64, col: u64, admin: &address, diff --git a/sui_programmability/examples/nfts/sources/Auction.move b/sui_programmability/examples/nfts/sources/Auction.move index 6e0983a33b701..80a36a428cd15 100644 --- a/sui_programmability/examples/nfts/sources/Auction.move +++ b/sui_programmability/examples/nfts/sources/Auction.move @@ -85,7 +85,7 @@ module NFTs::Auction { /// Updates the auction based on the information in the bid /// (update auction if higher bid received and send coin back for /// bids that are too low). This is executed by the auctioneer. - public fun update_auction(auction: &mut Auction, bid: Bid, _ctx: &mut TxContext) { + public(script) fun update_auction(auction: &mut Auction, bid: Bid, _ctx: &mut TxContext) { let Bid { id, bidder, auction_id, coin } = bid; ID::delete(id); assert!(AuctionLib::auction_id(auction) == &auction_id, EWRONG_AUCTION); @@ -95,7 +95,7 @@ module NFTs::Auction { /// Ends the auction - transfers item to the currently highest /// bidder or to the original owner if no bids have been /// placed. This is executed by the auctioneer. - public fun end_auction(auction: Auction, _ctx: &mut TxContext) { + public(script) fun end_auction(auction: Auction, _ctx: &mut TxContext) { AuctionLib::end_and_destroy_auction(auction); } } diff --git a/sui_programmability/examples/nfts/sources/DiscountCoupon.move b/sui_programmability/examples/nfts/sources/DiscountCoupon.move index f2101211b5341..69b0ae8355fd9 100644 --- a/sui_programmability/examples/nfts/sources/DiscountCoupon.move +++ b/sui_programmability/examples/nfts/sources/DiscountCoupon.move @@ -30,8 +30,8 @@ module NFTs::DiscountCoupon { } /// Mint then transfer a new `DiscountCoupon` NFT, and top up recipient with some SUI. - public fun mint_and_topup( - coin: Coin::Coin, + public(script) fun mint_and_topup( + coin: Coin::Coin, discount: u8, expiration: u64, recipient: address, @@ -39,25 +39,25 @@ module NFTs::DiscountCoupon { ) { assert!(discount > 0 && discount <= 100, EOUT_OF_RANGE_DISCOUNT); let nft = NFT::mint( - DiscountCoupon { + DiscountCoupon { issuer: TxContext::sender(ctx), - discount, + discount, expiration, - }, + }, ctx); Transfer::transfer(nft, recipient); Sui::SUI::transfer(coin, recipient, ctx); } /// Burn DiscountCoupon. - public fun burn(nft: NFT, _ctx: &mut TxContext) { + public(script) fun burn(nft: NFT, _ctx: &mut TxContext) { let DiscountCoupon { issuer: _, discount: _, expiration: _ } = NFT::burn(nft); } /// Transfer DiscountCoupon to issuer only. - // TODO: Consider adding more valid recipients. + // TODO: Consider adding more valid recipients. // If we stick with issuer-as-receiver only, then `recipient` input won't be required). - public fun transfer(nft: NFT, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(nft: NFT, recipient: address, _ctx: &mut TxContext) { assert!(NFT::data(&nft).issuer == recipient, EWRONG_RECIPIENT); NFT::transfer(nft, recipient) } diff --git a/sui_programmability/examples/nfts/sources/Geniteam.move b/sui_programmability/examples/nfts/sources/Geniteam.move index 2584aa8d1f9e9..5d31014dc2b1e 100644 --- a/sui_programmability/examples/nfts/sources/Geniteam.move +++ b/sui_programmability/examples/nfts/sources/Geniteam.move @@ -94,7 +94,7 @@ module NFTs::Geniteam { // ============================ Entry functions ============================ /// Create a player and transfer it to the transaction sender - public fun create_player( + public(script) fun create_player( player_name: vector, ctx: &mut TxContext ) { // Create player simply and transfer to caller @@ -103,7 +103,7 @@ module NFTs::Geniteam { } /// Create a Farm and add it to the Player - public fun create_farm( + public(script) fun create_farm( player: &mut Player, farm_img_index: u64, farm_name: vector, total_monster_slots: u64, ctx: &mut TxContext ) { @@ -121,7 +121,7 @@ module NFTs::Geniteam { /// Create a Monster and add it to the Farm's collection of Monsters, which /// is unbounded - public fun create_monster(_player: &mut Player, + public(script) fun create_monster(_player: &mut Player, farm: &mut Farm, pet_monsters: &mut Collection, monster_name: vector, diff --git a/sui_programmability/examples/nfts/sources/Marketplace.move b/sui_programmability/examples/nfts/sources/Marketplace.move index 4d2807df2c831..fe15426c6658d 100644 --- a/sui_programmability/examples/nfts/sources/Marketplace.move +++ b/sui_programmability/examples/nfts/sources/Marketplace.move @@ -34,7 +34,7 @@ module NFTs::Marketplace { } /// Create a new shared Marketplace. - public fun create(ctx: &mut TxContext) { + public(script) fun create(ctx: &mut TxContext) { let id = TxContext::new_id(ctx); let objects = Bag::new(ctx); let (id, objects) = Transfer::transfer_to_object_id(objects, id); @@ -46,7 +46,7 @@ module NFTs::Marketplace { } /// List an item at the Marketplace. - public fun list( + public(script) fun list( _marketplace: &Marketplace, objects: &mut Bag, item: T, @@ -63,7 +63,7 @@ module NFTs::Marketplace { } /// Remove listing and get an item back. Only owner can do that. - public fun delist( + public(script) fun delist( _marketplace: &Marketplace, objects: &mut Bag, listing: Listing, @@ -79,7 +79,7 @@ module NFTs::Marketplace { } /// Call [`delist`] and transfer item to the sender. - public fun delist_and_take( + public(script) fun delist_and_take( _marketplace: &Marketplace, objects: &mut Bag, listing: Listing, @@ -107,7 +107,7 @@ module NFTs::Marketplace { } /// Call [`buy`] and transfer item to the sender. - public fun buy_and_take( + public(script) fun buy_and_take( _marketplace: &Marketplace, listing: Listing, objects: &mut Bag, @@ -148,7 +148,7 @@ module NFTs::MarketplaceTests { const BUYER: address = @0x00B; /// Create a shared [`Marketplace`]. - fun create_marketplace(scenario: &mut Scenario) { + public(script) fun create_marketplace(scenario: &mut Scenario) { TestScenario::next_tx(scenario, &ADMIN); Marketplace::create(TestScenario::ctx(scenario)); } @@ -168,7 +168,7 @@ module NFTs::MarketplaceTests { } // SELLER lists KITTY at the Marketplace for 100 SUI. - fun list_kitty(scenario: &mut Scenario) { + public(script) fun list_kitty(scenario: &mut Scenario) { TestScenario::next_tx(scenario, &SELLER); let mkp = TestScenario::take_object(scenario); let bag = TestScenario::take_nested_object(scenario, &mkp); @@ -180,7 +180,7 @@ module NFTs::MarketplaceTests { } #[test] - fun list_and_delist() { + public(script) fun list_and_delist() { let scenario = &mut TestScenario::begin(&ADMIN); create_marketplace(scenario); @@ -206,7 +206,7 @@ module NFTs::MarketplaceTests { #[test] #[expected_failure(abort_code = 1)] - fun fail_to_delist() { + public(script) fun fail_to_delist() { let scenario = &mut TestScenario::begin(&ADMIN); create_marketplace(scenario); @@ -231,7 +231,7 @@ module NFTs::MarketplaceTests { } #[test] - fun buy_kitty() { + public(script) fun buy_kitty() { let scenario = &mut TestScenario::begin(&ADMIN); create_marketplace(scenario); @@ -262,7 +262,7 @@ module NFTs::MarketplaceTests { #[test] #[expected_failure(abort_code = 0)] - fun fail_to_buy() { + public(script) fun fail_to_buy() { let scenario = &mut TestScenario::begin(&ADMIN); create_marketplace(scenario); diff --git a/sui_programmability/examples/nfts/sources/SharedAuction.move b/sui_programmability/examples/nfts/sources/SharedAuction.move index 30f220f273f45..48bfa7d2da346 100644 --- a/sui_programmability/examples/nfts/sources/SharedAuction.move +++ b/sui_programmability/examples/nfts/sources/SharedAuction.move @@ -42,7 +42,7 @@ module NFTs::SharedAuction { /// Creates an auction. This is executed by the owner of the asset /// to be auctioned. - public fun create_auction(to_sell: T, ctx: &mut TxContext) { + public(script) fun create_auction(to_sell: T, ctx: &mut TxContext) { let auction = AuctionLib::create_auction(TxContext::new_id(ctx), to_sell, ctx); Transfer::share_object(auction); } @@ -51,7 +51,7 @@ module NFTs::SharedAuction { /// change of the auction state (if bid was high enough) or return /// of the funds (if the bid was too low). This is executed by a /// bidder. - public fun bid(coin: Coin, auction: &mut Auction, ctx: &mut TxContext) { + public(script) fun bid(coin: Coin, auction: &mut Auction, ctx: &mut TxContext) { let bidder = TxContext::sender(ctx); AuctionLib::update_auction(auction, bidder, coin); } @@ -60,7 +60,7 @@ module NFTs::SharedAuction { /// bidder or back to the original owner if no bids have been /// placed. This is executed by the owner of the asset to be /// auctioned. - public fun end_auction(auction: &mut Auction, ctx: &mut TxContext) { + public(script) fun end_auction(auction: &mut Auction, ctx: &mut TxContext) { let owner = AuctionLib::auction_owner(auction); assert!(TxContext::sender(ctx) == owner, EWRONG_OWNER); AuctionLib::end_shared_auction(auction); diff --git a/sui_programmability/examples/nfts/tests/AuctionTests.move b/sui_programmability/examples/nfts/tests/AuctionTests.move index eeb40fca5bf30..d558159ca6b48 100644 --- a/sui_programmability/examples/nfts/tests/AuctionTests.move +++ b/sui_programmability/examples/nfts/tests/AuctionTests.move @@ -35,7 +35,7 @@ module NFTs::AuctionTests { } #[test] - public fun simple_auction_test() { + public(script) fun simple_auction_test() { let auctioneer = @0xABBA; let owner = @0xACE; let bidder1 = @0xFACE; diff --git a/sui_programmability/examples/nfts/tests/DiscountCouponTests.move b/sui_programmability/examples/nfts/tests/DiscountCouponTests.move index bdf0733280454..431c8570b400a 100644 --- a/sui_programmability/examples/nfts/tests/DiscountCouponTests.move +++ b/sui_programmability/examples/nfts/tests/DiscountCouponTests.move @@ -26,7 +26,7 @@ module NFTs::DiscountCouponTests { } #[test] - public fun test_mint_then_transfer() { + public(script) fun test_mint_then_transfer() { let scenario = &mut TestScenario::begin(&ISSUER_ADDRESS); { init(TestScenario::ctx(scenario)); diff --git a/sui_programmability/examples/nfts/tests/SharedAuctionTests.move b/sui_programmability/examples/nfts/tests/SharedAuctionTests.move index 791831b0a2a53..d23bfdc4d2d2b 100644 --- a/sui_programmability/examples/nfts/tests/SharedAuctionTests.move +++ b/sui_programmability/examples/nfts/tests/SharedAuctionTests.move @@ -39,7 +39,7 @@ module NFTs::SharedAuctionTests { } #[test] - public fun simple_auction_test() { + public(script) fun simple_auction_test() { let admin = @0xABBA; // needed only to initialize "state of the world" let owner = @0xACE; let bidder1 = @0xFACE; diff --git a/sui_programmability/framework/sources/Bag.move b/sui_programmability/framework/sources/Bag.move index 6a8968d3fede3..afda62fe3265d 100644 --- a/sui_programmability/framework/sources/Bag.move +++ b/sui_programmability/framework/sources/Bag.move @@ -61,7 +61,7 @@ module Sui::Bag { } /// Create a new Bag and transfer it to the signer. - public fun create(ctx: &mut TxContext) { + public(script) fun create(ctx: &mut TxContext) { Transfer::transfer(new(ctx), TxContext::sender(ctx)) } @@ -118,13 +118,13 @@ module Sui::Bag { } /// Remove the object from the Bag, and then transfer it to the signer. - public fun remove_and_take(c: &mut Bag, object: T, ctx: &mut TxContext) { + public(script) fun remove_and_take(c: &mut Bag, object: T, ctx: &mut TxContext) { let object = remove(c, object); Transfer::transfer(object, TxContext::sender(ctx)); } /// Transfer the entire Bag to `recipient`. - public fun transfer(c: Bag, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(c: Bag, recipient: address, _ctx: &mut TxContext) { Transfer::transfer(c, recipient) } diff --git a/sui_programmability/framework/sources/Coin.move b/sui_programmability/framework/sources/Coin.move index a08a304eed242..3bcb4952d86b9 100644 --- a/sui_programmability/framework/sources/Coin.move +++ b/sui_programmability/framework/sources/Coin.move @@ -138,31 +138,31 @@ module Sui::Coin { /// Send `amount` units of `c` to `recipient /// Aborts with `EVALUE` if `amount` is greater than or equal to `amount` - public fun transfer_(c: &mut Coin, amount: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun transfer_(c: &mut Coin, amount: u64, recipient: address, ctx: &mut TxContext) { Transfer::transfer(withdraw(c, amount, ctx), recipient) } /// Consume the coin `c` and add its value to `self`. /// Aborts if `c.value + self.value > U64_MAX` - public fun join_(self: &mut Coin, c: Coin, _ctx: &mut TxContext) { + public(script) fun join_(self: &mut Coin, c: Coin, _ctx: &mut TxContext) { join(self, c) } /// Join everything in `coins` with `self` - public fun join_vec_(self: &mut Coin, coins: vector>, _ctx: &mut TxContext) { + public(script) fun join_vec_(self: &mut Coin, coins: vector>, _ctx: &mut TxContext) { join_vec(self, coins) } /// Split coin `self` to two coins, one with balance `split_amount`, /// and the remaining balance is left is `self`. - public fun split(self: &mut Coin, split_amount: u64, ctx: &mut TxContext) { + public(script) fun split(self: &mut Coin, split_amount: u64, ctx: &mut TxContext) { let new_coin = withdraw(self, split_amount, ctx); Transfer::transfer(new_coin, TxContext::sender(ctx)); } /// Split coin `self` into multiple coins, each with balance specified /// in `split_amounts`. Remaining balance is left in `self`. - public fun split_vec(self: &mut Coin, split_amounts: vector, ctx: &mut TxContext) { + public(script) fun split_vec(self: &mut Coin, split_amounts: vector, ctx: &mut TxContext) { let i = 0; let len = Vector::length(&split_amounts); while (i < len) { diff --git a/sui_programmability/framework/sources/Collection.move b/sui_programmability/framework/sources/Collection.move index 8d90f8be10c84..0e31dfb777498 100644 --- a/sui_programmability/framework/sources/Collection.move +++ b/sui_programmability/framework/sources/Collection.move @@ -66,7 +66,7 @@ module Sui::Collection { } /// Create a new Collection and transfer it to the signer. - public fun create(ctx: &mut TxContext) { + public(script) fun create(ctx: &mut TxContext) { Transfer::transfer(new(ctx), TxContext::sender(ctx)) } @@ -125,13 +125,13 @@ module Sui::Collection { } /// Remove the object from the collection, and then transfer it to the signer. - public fun remove_and_take(c: &mut Collection, object: T, ctx: &mut TxContext) { + public(script) fun remove_and_take(c: &mut Collection, object: T, ctx: &mut TxContext) { let (object, child_ref) = remove(c, object); Transfer::transfer_child_to_address(object, child_ref, TxContext::sender(ctx)); } /// Transfer the entire collection to `recipient`. - public fun transfer(c: Collection, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(c: Collection, recipient: address, _ctx: &mut TxContext) { Transfer::transfer(c, recipient) } diff --git a/sui_programmability/framework/sources/CrossChainAirdrop.move b/sui_programmability/framework/sources/CrossChainAirdrop.move index ed266e671bd84..d4b76343192a9 100644 --- a/sui_programmability/framework/sources/CrossChainAirdrop.move +++ b/sui_programmability/framework/sources/CrossChainAirdrop.move @@ -4,7 +4,7 @@ /// Allow a trusted oracle to mint a copy of NFT from a different chain. There can /// only be one copy for each unique pair of contract_address and token_id. We only /// support a single chain(Ethereum) right now, but this can be extended to other -/// chains by adding a chain_id field. +/// chains by adding a chain_id field. module Sui::CrossChainAirdrop { use Std::Vector; use Sui::ERC721Metadata::{Self, ERC721Metadata, TokenID}; @@ -17,7 +17,7 @@ module Sui::CrossChainAirdrop { struct CrossChainAirdropOracle has key { id: VersionedID, // TODO: replace this with SparseSet for O(1) on-chain uniqueness check - managed_contracts: vector, + managed_contracts: vector, } /// The address of the source contract @@ -37,7 +37,7 @@ module Sui::CrossChainAirdrop { // TODO: replace this with SparseSet for O(1) on-chain uniqueness check claimed_source_token_ids: vector } - + /// The Sui representation of the original ERC721 NFT on Eth struct ERC721 has store { /// The address of the source contract, e.g, the Ethereum contract address @@ -63,7 +63,7 @@ module Sui::CrossChainAirdrop { } /// Called by the oracle to mint the airdrop NFT and transfer to the recipient - public fun claim( + public(script) fun claim( oracle: &mut CrossChainAirdropOracle, recipient: address, source_contract_address: vector, @@ -77,7 +77,7 @@ module Sui::CrossChainAirdrop { // NOTE: this is where the globally uniqueness check happens assert!(!is_token_claimed(contract, &token_id), ETOKEN_ID_CLAIMED); let nft = NFT::mint( - ERC721 { + ERC721 { source_contract_address: SourceContractAddress { address: source_contract_address }, metadata: ERC721Metadata::new(token_id, name, token_uri), }, @@ -97,7 +97,7 @@ module Sui::CrossChainAirdrop { }; index = index + 1; }; - + create_contract(oracle, source_contract_address) } @@ -130,4 +130,3 @@ module Sui::CrossChainAirdrop { init(ctx) } } - diff --git a/sui_programmability/framework/sources/ObjectBasics.move b/sui_programmability/framework/sources/ObjectBasics.move index b1427cfcdd2f6..bfdb67c648bef 100644 --- a/sui_programmability/framework/sources/ObjectBasics.move +++ b/sui_programmability/framework/sources/ObjectBasics.move @@ -22,42 +22,42 @@ module Sui::ObjectBasics { new_value: u64 } - public fun create(value: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun create(value: u64, recipient: address, ctx: &mut TxContext) { Transfer::transfer( Object { id: TxContext::new_id(ctx), value }, recipient ) } - public fun transfer(o: Object, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(o: Object, recipient: address, _ctx: &mut TxContext) { Transfer::transfer(o, recipient) } - public fun freeze_object(o: Object, _ctx: &mut TxContext) { + public(script) fun freeze_object(o: Object, _ctx: &mut TxContext) { Transfer::freeze_object(o) } - public fun set_value(o: &mut Object, value: u64, _ctx: &mut TxContext) { + public(script) fun set_value(o: &mut Object, value: u64, _ctx: &mut TxContext) { o.value = value; } // test that reading o2 and updating o1 works - public fun update(o1: &mut Object, o2: &Object, _ctx: &mut TxContext) { + public(script) fun update(o1: &mut Object, o2: &Object, _ctx: &mut TxContext) { o1.value = o2.value; // emit an event so the world can see the new value Event::emit(NewValueEvent { new_value: o2.value }) } - public fun delete(o: Object, _ctx: &mut TxContext) { + public(script) fun delete(o: Object, _ctx: &mut TxContext) { let Object { id, value: _ } = o; ID::delete(id); } - public fun wrap(o: Object, ctx: &mut TxContext) { + public(script) fun wrap(o: Object, ctx: &mut TxContext) { Transfer::transfer(Wrapper { id: TxContext::new_id(ctx), o }, TxContext::sender(ctx)) } - public fun unwrap(w: Wrapper, ctx: &mut TxContext) { + public(script) fun unwrap(w: Wrapper, ctx: &mut TxContext) { let Wrapper { id, o } = w; ID::delete(id); Transfer::transfer(o, TxContext::sender(ctx)) diff --git a/sui_programmability/framework/sources/SUI.move b/sui_programmability/framework/sources/SUI.move index fd1fa96185c75..b828c1507d108 100644 --- a/sui_programmability/framework/sources/SUI.move +++ b/sui_programmability/framework/sources/SUI.move @@ -21,7 +21,7 @@ module Sui::SUI { } /// Transfer to a recipient - public fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { + public(script) fun transfer(c: Coin::Coin, recipient: address, _ctx: &mut TxContext) { Coin::transfer(c, recipient) } diff --git a/sui_programmability/framework/tests/BagTests.move b/sui_programmability/framework/tests/BagTests.move index d0f97cc4c8741..e33b7e5f34587 100644 --- a/sui_programmability/framework/tests/BagTests.move +++ b/sui_programmability/framework/tests/BagTests.move @@ -20,7 +20,7 @@ module Sui::BagTests { } #[test] - fun test_bag() { + public(script) fun test_bag() { let sender = @0x0; let scenario = &mut TestScenario::begin(&sender); @@ -55,7 +55,7 @@ module Sui::BagTests { #[test] #[expected_failure(abort_code = 520)] - fun test_init_with_invalid_max_capacity() { + public(script) fun test_init_with_invalid_max_capacity() { let ctx = TxContext::dummy(); // Sui::Bag::DEFAULT_MAX_CAPACITY is not readable outside the module let max_capacity = 65536; @@ -65,7 +65,7 @@ module Sui::BagTests { #[test] #[expected_failure(abort_code = 520)] - fun test_init_with_zero() { + public(script) fun test_init_with_zero() { let ctx = TxContext::dummy(); let bag = Bag::new_with_max_capacity(&mut ctx, 0); Bag::transfer(bag, TxContext::sender(&ctx), &mut ctx); @@ -73,7 +73,7 @@ module Sui::BagTests { #[test] #[expected_failure(abort_code = 776)] - fun test_exceed_max_capacity() { + public(script) fun test_exceed_max_capacity() { let ctx = TxContext::dummy(); let bag = Bag::new_with_max_capacity(&mut ctx, 1); diff --git a/sui_programmability/framework/tests/CollectionTests.move b/sui_programmability/framework/tests/CollectionTests.move index 2964f232b5f28..f8c02251aabef 100644 --- a/sui_programmability/framework/tests/CollectionTests.move +++ b/sui_programmability/framework/tests/CollectionTests.move @@ -14,7 +14,7 @@ module Sui::CollectionTests { } #[test] - fun test_collection() { + public(script) fun test_collection() { let sender = @0x0; let scenario = &mut TestScenario::begin(&sender); @@ -47,7 +47,7 @@ module Sui::CollectionTests { } #[test] - fun test_collection_bag_interaction() { + public(script) fun test_collection_bag_interaction() { let sender = @0x0; let scenario = &mut TestScenario::begin(&sender); @@ -109,7 +109,7 @@ module Sui::CollectionTests { #[test] #[expected_failure(abort_code = 520)] - fun test_init_with_invalid_max_capacity() { + public(script) fun test_init_with_invalid_max_capacity() { let ctx = TxContext::dummy(); // Sui::Collection::DEFAULT_MAX_CAPACITY is not readable outside the module let max_capacity = 65536; @@ -119,7 +119,7 @@ module Sui::CollectionTests { #[test] #[expected_failure(abort_code = 520)] - fun test_init_with_zero() { + public(script) fun test_init_with_zero() { let ctx = TxContext::dummy(); let collection = Collection::new_with_max_capacity(&mut ctx, 0); Collection::transfer(collection, TxContext::sender(&ctx), &mut ctx); @@ -127,7 +127,7 @@ module Sui::CollectionTests { #[test] #[expected_failure(abort_code = 776)] - fun test_exceed_max_capacity() { + public(script) fun test_exceed_max_capacity() { let ctx = TxContext::dummy(); let collection = Collection::new_with_max_capacity(&mut ctx, 1); diff --git a/sui_programmability/framework/tests/CrossChainAirdropTests.move b/sui_programmability/framework/tests/CrossChainAirdropTests.move index 6e9ec041a70bf..b583e86efcdd5 100644 --- a/sui_programmability/framework/tests/CrossChainAirdropTests.move +++ b/sui_programmability/framework/tests/CrossChainAirdropTests.move @@ -26,7 +26,7 @@ module Sui::CrossChainAirdropTests { } #[test] - fun test_claim_airdrop() { + public(script) fun test_claim_airdrop() { let (scenario, oracle_address) = init(); // claim a token @@ -38,7 +38,7 @@ module Sui::CrossChainAirdropTests { #[test] #[expected_failure(abort_code = 0)] - fun test_double_claim() { + public(script) fun test_double_claim() { let (scenario, oracle_address) = init(); // claim a token @@ -48,7 +48,7 @@ module Sui::CrossChainAirdropTests { claim_token(&mut scenario, &oracle_address, SOURCE_TOKEN_ID); } - fun init(): (Scenario, address) { + public(script) fun init(): (Scenario, address) { let scenario = TestScenario::begin(&ORACLE_ADDRESS); { let ctx = TestScenario::ctx(&mut scenario); @@ -57,7 +57,7 @@ module Sui::CrossChainAirdropTests { (scenario, ORACLE_ADDRESS) } - fun claim_token(scenario: &mut Scenario, oracle_address: &address, token_id: u64) { + public(script) fun claim_token(scenario: &mut Scenario, oracle_address: &address, token_id: u64) { TestScenario::next_tx(scenario, oracle_address); { let oracle = TestScenario::take_object(scenario); @@ -75,7 +75,7 @@ module Sui::CrossChainAirdropTests { }; } - fun owns_object(scenario: &mut Scenario, owner: &address): bool{ + public(script) fun owns_object(scenario: &mut Scenario, owner: &address): bool{ // Verify the token has been transfer to the recipient TestScenario::next_tx(scenario, owner); TestScenario::can_take_object>(scenario) diff --git a/sui_programmability/tutorial/sources/M1.move b/sui_programmability/tutorial/sources/M1.move index d4378e3688cbd..2d325c219d6d6 100644 --- a/sui_programmability/tutorial/sources/M1.move +++ b/sui_programmability/tutorial/sources/M1.move @@ -41,7 +41,7 @@ module MyFirstPackage::M1 { self.strength } - public fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { + public(script) fun sword_create(forge: &mut Forge, magic: u64, strength: u64, recipient: address, ctx: &mut TxContext) { use Sui::Transfer; use Sui::TxContext; // create a sword @@ -55,7 +55,7 @@ module MyFirstPackage::M1 { forge.swords_created = forge.swords_created + 1; } - public fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) { + public(script) fun sword_transfer(sword: Sword, recipient: address, _ctx: &mut TxContext) { use Sui::Transfer; // transfer the sword Transfer::transfer(sword, recipient); diff --git a/sui_programmability/verifier/src/entry_function_param_verifier.rs b/sui_programmability/verifier/src/entry_function_param_verifier.rs deleted file mode 100644 index 2ae9ac2bdbad1..0000000000000 --- a/sui_programmability/verifier/src/entry_function_param_verifier.rs +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright (c) 2022, Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use move_binary_format::{ - binary_views::BinaryIndexedView, - file_format::{Ability, FunctionHandle, Signature, SignatureToken, Visibility}, - CompiledModule, -}; -use sui_types::{ - base_types::{TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME}, - error::{SuiError, SuiResult}, - SUI_FRAMEWORK_ADDRESS, -}; - -pub fn verify_module(module: &CompiledModule) -> SuiResult { - check_params(module) -} -/// Checks if parameters of functions that can become entry -/// functions (functions called directly from Sui) have correct types. -/// -/// We first identify functions that can be entry functions by looking -/// for functions with the following properties: -/// 1. Public -/// 2. Primitive return types (or vector of such up to 2 levels of nesting) -/// 3. Parameter order: objects, primitives, &mut TxContext -/// -/// Note that this can be ambiguous in presence of the following -/// templated parameters: -/// - param: T -/// - param: vector // and nested vectors -/// -/// A function is considered an entry function if such templated -/// arguments are part of "object parameters" only. -/// -/// In order for the parameter types of an entry function to be -/// correct, all generic types used in templated arguments mentioned -/// above must have the `key` ability. -pub fn check_params(module: &CompiledModule) -> SuiResult { - let view = BinaryIndexedView::Module(module); - for func_def in module.function_defs.iter() { - // find candidate entry functions and checke their parameters - // (ignore other functions) - if func_def.visibility != Visibility::Public { - // it's not an entry function as a non-public function - // cannot be called from Sui - continue; - } - let handle = view.function_handle_at(func_def.function); - - if view - .signature_at(handle.return_) - .0 - .iter() - .any(|ret_type| !is_entry_ret_type(ret_type)) - { - // it's not an entry function as it returns a value of - // types unsupported in entry functions - continue; - } - - let params = view.signature_at(handle.parameters); - let param_num = match is_entry_candidate(&view, params) { - Some(v) => v, - None => continue, - }; - // iterate over all object params and make sure that each - // template-typed (either by itself or in a vector) argument - // has the Key ability - for (pos, p) in params.0[0..param_num].iter().enumerate() { - if let SignatureToken::TypeParameter(_) = p { - if !is_template_param_ok(handle, p) { - return Err(SuiError::ModuleVerificationFailure { - error: format!( - "No `key` ability for the template parameter at position {} in entry function {}", - pos, - view.identifier_at(handle.name) - ), - }); - } - } - if let SignatureToken::Vector(_) = p { - if !is_template_vector_param_ok(handle, p) { - return Err(SuiError::ModuleVerificationFailure { - error: format!( - "No `key` ability for the template vector parameter at position {} in entry function {}", - pos, - view.identifier_at(handle.name) - ), - }); - } - } - } - } - Ok(()) -} - -/// Checks if a function can possibly be an entry function (without -/// checking correctness of the function's parameters). -fn is_entry_candidate(view: &BinaryIndexedView, params: &Signature) -> Option { - let mut obj_params_num = 0; - if params.is_empty() { - // must have at least on &mut TxContext param - return None; - } - let last_param = params.0.get(params.len() - 1).unwrap(); - if !is_tx_context(view, last_param) { - return None; - } - if params.len() == 1 { - // only one &mut TxContext param - return Some(obj_params_num); - } - // currently, an entry function has object parameters followed by - // primitive type parameters (followed by the &mut TxContext - // param, but we already checked this one) - let mut primitive_params_phase = false; // becomes true once we start seeing primitive type params - for p in ¶ms.0[0..params.len() - 1] { - if is_primitive(p) { - primitive_params_phase = true; - } else { - obj_params_num += 1; - if primitive_params_phase { - // We encounter a non primitive type parameter after - // the first one was encountered. This cannot be an - // entry function as it would get rejected by the - // resolve_and_type_check function in the adapter upon - // its call attempt from Sui - return None; - } - if !is_object(view, p) - && !is_template(p) - && !is_object_vector(view, p) - && !is_template_vector(p) - { - // A non-primitive type for entry functions must be an - // object, or a generic type, or a vector (possibly - // nested) of objects, or a templeted vector (possibly - // nested). Otherwise it is not an entry function as - // we cannot pass non-object types from Sui). - return None; - } - } - } - Some(obj_params_num) -} - -/// Checks if a given parameter is of a primitive type. It's a mirror -/// of the is_primitive function in the adapter module that operates -/// on Type-s. -fn is_primitive(p: &SignatureToken) -> bool { - // nested vectors of primitive types are OK to arbitrary nesting - // level - is_primitive_internal(p, 0, u32::MAX) -} - -// Checks if a given type is the correct entry function return type. It's a mirror -/// of the is_entry_ret_type function in the adapter module that -/// operates on Type-s. -pub fn is_entry_ret_type(t: &SignatureToken) -> bool { - // allow vectors of vectors but no deeper nesting - is_primitive_internal(t, 0, 2) -} - -fn is_primitive_internal(p: &SignatureToken, depth: u32, max_depth: u32) -> bool { - use SignatureToken::*; - match p { - Bool | U8 | U64 | U128 | Address => true, - Vector(t) => { - if depth < max_depth { - is_primitive_internal(t, depth + 1, max_depth) - } else { - false - } - } - Signer - | Struct(_) - | StructInstantiation(..) - | Reference(_) - | MutableReference(_) - | TypeParameter(_) => false, - } -} - -fn is_object(view: &BinaryIndexedView, p: &SignatureToken) -> bool { - use SignatureToken::*; - match p { - Struct(idx) => view - .struct_handle_at(*idx) - .abilities - .has_ability(Ability::Key), - StructInstantiation(idx, _) => view - .struct_handle_at(*idx) - .abilities - .has_ability(Ability::Key), - Reference(t) => is_object(view, t), - MutableReference(t) => is_object(view, t), - _ => false, - } -} - -fn is_template(p: &SignatureToken) -> bool { - matches!(p, SignatureToken::TypeParameter(_)) -} - -fn is_object_vector(view: &BinaryIndexedView, p: &SignatureToken) -> bool { - if let SignatureToken::Vector(t) = p { - match &**t { - SignatureToken::Vector(inner_t) => return is_object_vector(view, inner_t), - other => return is_object(view, other), - } - } - false -} - -fn is_template_vector(p: &SignatureToken) -> bool { - match p { - SignatureToken::Vector(t) => is_template_vector(t), - other => matches!(other, SignatureToken::TypeParameter(_)), - } -} - -fn is_template_param_ok(handle: &FunctionHandle, p: &SignatureToken) -> bool { - if let SignatureToken::TypeParameter(idx) = p { - if !handle - .type_parameters - .get(*idx as usize) - .unwrap() - .has_ability(Ability::Key) - { - return false; - } - } - true -} - -fn is_template_vector_param_ok(handle: &FunctionHandle, p: &SignatureToken) -> bool { - match p { - SignatureToken::Vector(t) => is_template_vector_param_ok(handle, t), - SignatureToken::TypeParameter(_) => is_template_param_ok(handle, p), - _ => true, - } -} - -/// It's a mirror of the is_param_tx_context function in the adapter -/// module that operates on Type-s. -fn is_tx_context(view: &BinaryIndexedView, p: &SignatureToken) -> bool { - match p { - SignatureToken::MutableReference(m) => match &**m { - SignatureToken::Struct(idx) => { - let struct_handle = view.struct_handle_at(*idx); - let struct_name = view.identifier_at(struct_handle.name); - let module = view.module_handle_at(struct_handle.module); - let module_name = view.identifier_at(module.name); - let module_addr = view.address_identifier_at(module.address); - module_name == TX_CONTEXT_MODULE_NAME - && module_addr == &SUI_FRAMEWORK_ADDRESS - && struct_name == TX_CONTEXT_STRUCT_NAME - } - _ => false, - }, - _ => false, - } -} diff --git a/sui_programmability/verifier/src/entry_points_verifier.rs b/sui_programmability/verifier/src/entry_points_verifier.rs new file mode 100644 index 0000000000000..55db60016272e --- /dev/null +++ b/sui_programmability/verifier/src/entry_points_verifier.rs @@ -0,0 +1,325 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use move_binary_format::{ + access::ModuleAccess, + binary_views::BinaryIndexedView, + file_format::{AbilitySet, FunctionDefinition, SignatureToken, Visibility}, + CompiledModule, +}; +use move_core_types::{ident_str, identifier::IdentStr, language_storage::TypeTag}; +use sui_types::{ + base_types::{TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME}, + error::{SuiError, SuiResult}, + SUI_FRAMEWORK_ADDRESS, +}; + +use crate::format_signature_token; + +pub const INIT_FN_NAME: &IdentStr = ident_str!("init"); + +/// Checks if parameters of functions that can become entry +/// functions (functions called directly from Sui) have correct types. +/// +/// We first identify functions that can be entry functions by looking +/// for functions with the following properties: +/// 1. Public +/// 2. Primitive return types (or vector of such up to 2 levels of nesting) +/// 3. Parameter order: objects, primitives, &mut TxContext +/// +/// Note that this can be ambiguous in presence of the following +/// templated parameters: +/// - param: T +/// - param: vector // and nested vectors +/// +/// A function is considered an entry function if such templated +/// arguments are part of "object parameters" only. +/// +/// In order for the parameter types of an entry function to be +/// correct, all generic types used in templated arguments mentioned +/// above must have the `key` ability. +pub fn verify_module(module: &CompiledModule) -> SuiResult { + for func_def in &module.function_defs { + // find candidate entry functions and checke their parameters + // (ignore other functions) + if func_def.visibility != Visibility::Script { + // it's not an entry function as a non-script function + // cannot be called from Sui + continue; + } + let is_entrypoint_execution = false; + verify_entry_function_impl(module, func_def, None, is_entrypoint_execution) + .map_err(|error| SuiError::ModuleVerificationFailure { error })?; + } + Ok(()) +} + +/// Checks if this module has a conformant `init` +// TODO make this static +pub fn module_has_init(module: &CompiledModule) -> bool { + let view = BinaryIndexedView::Module(module); + let fdef_opt = module.function_defs.iter().find(|fdef| { + let handle = view.function_handle_at(fdef.function); + let name = view.identifier_at(handle.name); + name == INIT_FN_NAME + }); + let fdef = match fdef_opt { + None => return false, + Some(fdef) => fdef, + }; + if fdef.visibility != Visibility::Private { + return false; + } + + let fhandle = module.function_handle_at(fdef.function); + if !fhandle.type_parameters.is_empty() { + return false; + } + + if !view.signature_at(fhandle.return_).0.is_empty() { + return false; + } + + let parameters = &view.signature_at(fhandle.parameters).0; + if parameters.len() != 1 { + return false; + } + + is_tx_context(&view, ¶meters[0]) +} + +pub fn verify_entry_function( + module: &CompiledModule, + func_def: &FunctionDefinition, + function_type_args: &[TypeTag], +) -> SuiResult { + let is_entrypoint_execution = true; + verify_entry_function_impl( + module, + func_def, + Some(function_type_args), + is_entrypoint_execution, + ) + .map_err(|error| SuiError::InvalidFunctionSignature { error }) +} + +fn verify_entry_function_impl( + module: &CompiledModule, + func_def: &FunctionDefinition, + function_type_args: Option<&[TypeTag]>, + _is_entrypoint_execution: bool, +) -> Result<(), String> { + let view = BinaryIndexedView::Module(module); + let handle = view.function_handle_at(func_def.function); + let params = view.signature_at(handle.parameters); + let pessimistic_type_args = &handle.type_parameters; + + // must have at least on &mut TxContext param + if params.is_empty() { + debug_assert!(!_is_entrypoint_execution); + return Err(format!( + "No parameters in entry function {}", + view.identifier_at(handle.name) + )); + } + let last_param = params.0.last().unwrap(); + if !is_tx_context(&view, last_param) { + debug_assert!(!_is_entrypoint_execution); + return Err(format!( + "{}::{}. Expected last parameter of function signature to be &mut {}::{}::{}, but found {}", + module.self_id(), + view.identifier_at(handle.name), + SUI_FRAMEWORK_ADDRESS, + TX_CONTEXT_MODULE_NAME, + TX_CONTEXT_STRUCT_NAME, + format_signature_token(module, last_param), + )); + } + + let last_idx = params.0.len() - 1; + let all_but_last = ¶ms.0[..last_idx]; + let mut first_prim_idx_opt = None; + for (idx, param_sig_token) in all_but_last.iter().enumerate() { + if !is_object(&view, pessimistic_type_args, param_sig_token)? { + first_prim_idx_opt = Some(idx); + break; + } + } + // if none, using last_idx will result in prim_suffix being empty + let first_prim_idx = first_prim_idx_opt.unwrap_or(last_idx); + let prim_suffix = &all_but_last[first_prim_idx..]; + for (pos, t) in prim_suffix + .iter() + .enumerate() + .map(|(idx, s)| (idx + first_prim_idx, s)) + { + if !is_primitive(function_type_args, pessimistic_type_args, t) { + return Err(format!( + "{}::{}. Expected primitive parameter after object parameters for function {} at \ + position {}", + module.self_id(), + view.identifier_at(handle.name), + view.identifier_at(handle.name), + pos, + )); + } + } + + for (pos, ret_t) in view.signature_at(handle.return_).0.iter().enumerate() { + if !is_entry_ret_type(function_type_args, pessimistic_type_args, ret_t) { + return Err(format!( + "{}::{}. Expected primitive return type for function {} at position {}", + module.self_id(), + view.identifier_at(handle.name), + view.identifier_at(handle.name), + pos, + )); + } + } + + Ok(()) +} + +fn is_tx_context(view: &BinaryIndexedView, p: &SignatureToken) -> bool { + match p { + SignatureToken::MutableReference(m) => match &**m { + SignatureToken::Struct(idx) => { + let struct_handle = view.struct_handle_at(*idx); + let struct_name = view.identifier_at(struct_handle.name); + let module = view.module_handle_at(struct_handle.module); + let module_name = view.identifier_at(module.name); + let module_addr = view.address_identifier_at(module.address); + module_name == TX_CONTEXT_MODULE_NAME + && module_addr == &SUI_FRAMEWORK_ADDRESS + && struct_name == TX_CONTEXT_STRUCT_NAME + } + _ => false, + }, + _ => false, + } +} + +pub fn is_object( + view: &BinaryIndexedView, + function_type_args: &[AbilitySet], + t: &SignatureToken, +) -> Result { + use SignatureToken as S; + match t { + S::Reference(inner) | S::MutableReference(inner) | S::Vector(inner) => { + is_object(view, function_type_args, inner) + } + _ => is_object_struct(view, function_type_args, t), + } +} + +fn is_object_struct( + view: &BinaryIndexedView, + function_type_args: &[AbilitySet], + s: &SignatureToken, +) -> Result { + use SignatureToken as S; + match s { + S::Bool + | S::U8 + | S::U64 + | S::U128 + | S::Address + | S::Signer + | S::Vector(_) + | S::Reference(_) + | S::MutableReference(_) => Ok(false), + S::TypeParameter(idx) => Ok(function_type_args + .get(*idx as usize) + .map(|abs| abs.has_key()) + .unwrap_or(false)), + S::Struct(_) | S::StructInstantiation(_, _) => { + let abilities = view + .abilities(s, function_type_args) + .map_err(|vm_err| vm_err.to_string())?; + Ok(abilities.has_key()) + } + } +} + +/// Checks if a given parameter is of a primitive type. It's a mirror +/// of the is_primitive function in the adapter module that operates +/// on Type-s. +fn is_primitive( + type_args: Option<&[TypeTag]>, + function_type_constraints: &[AbilitySet], + t: &SignatureToken, +) -> bool { + // nested vectors of primitive types are OK to arbitrary nesting + // level + is_primitive_with_depth(type_args, function_type_constraints, t, 0, u32::MAX) +} + +// Checks if a given type is the correct entry function return type. It's a mirror +/// of the is_entry_ret_type function in the adapter module that +/// operates on Type-s. +fn is_entry_ret_type( + type_args: Option<&[TypeTag]>, + function_type_constraints: &[AbilitySet], + t: &SignatureToken, +) -> bool { + // allow vectors of vectors but no deeper nesting + is_primitive_with_depth(type_args, function_type_constraints, t, 0, 2) +} + +fn is_primitive_with_depth( + type_args: Option<&[TypeTag]>, + function_type_constraints: &[AbilitySet], + t: &SignatureToken, + depth: u32, + max_depth: u32, +) -> bool { + use SignatureToken as S; + match t { + S::Bool | S::U8 | S::U64 | S::U128 | S::Address => true, + + // type parameters are optimistically considered primitives if no type args are given + // if the type argument is out of bounds, that will be an error elsewhere + S::TypeParameter(idx) => { + let idx = *idx as usize; + let constrained_by_key = function_type_constraints + .get(idx) + .map(|abs| abs.has_key()) + .unwrap_or(false); + constrained_by_key + || type_args + .and_then(|type_args| { + type_args + .get(idx) + .map(|tt| is_primitive_vm_type_with_depth(tt, depth, max_depth)) + }) + .unwrap_or(true) + } + + S::Vector(inner) if depth < max_depth => is_primitive_with_depth( + type_args, + function_type_constraints, + inner, + depth + 1, + max_depth, + ), + + S::Vector(_) + | S::Signer + | S::Struct(_) + | S::StructInstantiation(..) + | S::Reference(_) + | S::MutableReference(_) => false, + } +} + +fn is_primitive_vm_type_with_depth(tt: &TypeTag, depth: u32, max_depth: u32) -> bool { + use TypeTag as T; + match tt { + T::Bool | T::U8 | T::U64 | T::U128 | T::Address => true, + T::Vector(inner) if depth < max_depth => { + is_primitive_vm_type_with_depth(inner, depth + 1, max_depth) + } + T::Vector(_) | T::Struct(_) | T::Signer => false, + } +} diff --git a/sui_programmability/verifier/src/lib.rs b/sui_programmability/verifier/src/lib.rs index 02748f5e881f2..897d6bc0e6bab 100644 --- a/sui_programmability/verifier/src/lib.rs +++ b/sui_programmability/verifier/src/lib.rs @@ -3,14 +3,86 @@ pub mod verifier; -pub mod entry_function_param_verifier; +pub mod entry_points_verifier; pub mod global_storage_access_verifier; pub mod id_immutable_verifier; pub mod id_leak_verifier; pub mod struct_with_key_verifier; +use move_binary_format::{ + access::ModuleAccess, + file_format::{SignatureToken, StructHandleIndex}, + CompiledModule, +}; +use move_core_types::{account_address::AccountAddress, identifier::IdentStr}; use sui_types::error::SuiError; fn verification_failure(error: String) -> SuiError { SuiError::ModuleVerificationFailure { error } } + +// TODO move these to move bytecode utils +pub fn resolve_struct( + module: &CompiledModule, + sidx: StructHandleIndex, +) -> (&AccountAddress, &IdentStr, &IdentStr) { + let shandle = module.struct_handle_at(sidx); + let mhandle = module.module_handle_at(shandle.module); + let address = module.address_identifier_at(mhandle.address); + let module_name = module.identifier_at(mhandle.name); + let struct_name = module.identifier_at(shandle.name); + (address, module_name, struct_name) +} + +pub fn format_signature_token(module: &CompiledModule, t: &SignatureToken) -> String { + match t { + SignatureToken::Bool => "bool".to_string(), + SignatureToken::U8 => "u8".to_string(), + SignatureToken::U64 => "u64".to_string(), + SignatureToken::U128 => "u128".to_string(), + SignatureToken::Address => "address".to_string(), + SignatureToken::Signer => "signer".to_string(), + SignatureToken::Vector(inner) => { + format!("vector<{}>", format_signature_token(module, inner)) + } + SignatureToken::Reference(inner) => format!("&{}", format_signature_token(module, inner)), + SignatureToken::MutableReference(inner) => { + format!("&mut {}", format_signature_token(module, inner)) + } + SignatureToken::TypeParameter(i) => format!("T{}", i), + + SignatureToken::Struct(idx) => format_signature_token_struct(module, *idx, &[]), + SignatureToken::StructInstantiation(idx, ty_args) => { + format_signature_token_struct(module, *idx, ty_args) + } + } +} + +pub fn format_signature_token_struct( + module: &CompiledModule, + sidx: StructHandleIndex, + ty_args: &[SignatureToken], +) -> String { + let (address, module_name, struct_name) = resolve_struct(module, sidx); + let s; + let ty_args_string = if ty_args.is_empty() { + "" + } else { + s = format!( + "<{}>", + ty_args + .iter() + .map(|t| format_signature_token(module, t)) + .collect::>() + .join(", ") + ); + &s + }; + format!( + "0x{}::{}::{}{}", + address.short_str_lossless(), + module_name, + struct_name, + ty_args_string + ) +} diff --git a/sui_programmability/verifier/src/verifier.rs b/sui_programmability/verifier/src/verifier.rs index e47b05a1cd48c..51362012f2eb8 100644 --- a/sui_programmability/verifier/src/verifier.rs +++ b/sui_programmability/verifier/src/verifier.rs @@ -7,8 +7,8 @@ use move_binary_format::file_format::CompiledModule; use sui_types::error::SuiResult; use crate::{ - entry_function_param_verifier, global_storage_access_verifier, id_immutable_verifier, - id_leak_verifier, struct_with_key_verifier, + entry_points_verifier, global_storage_access_verifier, id_immutable_verifier, id_leak_verifier, + struct_with_key_verifier, }; /// Helper for a "canonical" verification of a module. @@ -17,5 +17,5 @@ pub fn verify_module(module: &CompiledModule) -> SuiResult { global_storage_access_verifier::verify_module(module)?; id_immutable_verifier::verify_module(module)?; id_leak_verifier::verify_module(module)?; - entry_function_param_verifier::verify_module(module) + entry_points_verifier::verify_module(module) } diff --git a/sui_programmability/verifier/tests/entry_function_param_verification.rs b/sui_programmability/verifier/tests/entry_points_verification.rs similarity index 91% rename from sui_programmability/verifier/tests/entry_function_param_verification.rs rename to sui_programmability/verifier/tests/entry_points_verification.rs index 69bb19df016fc..771c506af4b6e 100644 --- a/sui_programmability/verifier/tests/entry_function_param_verification.rs +++ b/sui_programmability/verifier/tests/entry_points_verification.rs @@ -15,7 +15,7 @@ use move_binary_format::{ use move_disassembler::disassembler::Disassembler; use move_ir_types::location::Spanned; use sui_types::SUI_FRAMEWORK_ADDRESS; -use sui_verifier::entry_function_param_verifier::verify_module; +use sui_verifier::entry_points_verifier::verify_module; fn add_function( builder: &mut ModuleBuilder, @@ -30,7 +30,7 @@ fn add_function( parameters, ret, type_parameters, - Visibility::Public, + Visibility::Script, CodeUnit { locals: SignatureIndex(1), // module_builder has "void" signature at 0 code: vec![Bytecode::Ret], // need some code otherwise will be considered native @@ -94,8 +94,7 @@ fn single_param() { public foo(loc0: Ty0, loc1: &mut TxContext): u64 { } - it's a valid entry function and verification should FAIL due to - missing Key abilty on the generic type + it's a valid entry function, loc0 is assumed to be primitive */ let (mut builder, _) = ModuleBuilder::default(); @@ -114,7 +113,7 @@ fn single_param() { let module = builder.get_module(); assert!( - verify_module(module).is_err(), + verify_module(module).is_ok(), "{}", format_assert_msg(module) ); @@ -187,7 +186,7 @@ fn single_template_object_param() { tx_context_param(tx_context), ], vec![], - vec![AbilitySet::EMPTY], + vec![AbilitySet::EMPTY | Ability::Store], ); let module = builder.get_module(); @@ -203,7 +202,7 @@ fn template_and_template_object_params() { /* struct ObjStruct has key - public foo(loc0: Ty0, loc1: ObjStruct, loc2: &mut TxContext) { + public foo(loc0: Ty0, loc1: ObjStruct, loc2: &mut TxContext) { } it's a valid entry function and verification should SUCCEED @@ -235,7 +234,10 @@ fn template_and_template_object_params() { tx_context_param(tx_context), ], vec![], - vec![AbilitySet::EMPTY | Ability::Key, AbilitySet::EMPTY], + vec![ + AbilitySet::EMPTY | Ability::Key, + AbilitySet::EMPTY | Ability::Store, + ], ); let module = builder.get_module(); @@ -254,9 +256,7 @@ fn template_param_after_primitive() { public foo(loc0: ObjStruct, loc1: u64, loc2: Ty0, loc3: &mut TxContext) { } - it's not a valid entry function (an entry function cannot have - template parameter after the first primitive type parameter) and - verification should SUCCEED + it is a valid entry function and verification should SUCCEED */ let (mut builder, _) = ModuleBuilder::default(); @@ -296,8 +296,7 @@ fn single_template_vector_param() { public foo(loc0: vector, loc1: &mut TxContext) { } - it's a valid entry function and verification should FAIL due to - missing Key abilty on the vector's generic type + it's a valid entry function, loc0 is assumed to be primitive */ let (mut builder, _) = ModuleBuilder::default(); @@ -316,7 +315,7 @@ fn single_template_vector_param() { let module = builder.get_module(); assert!( - verify_module(module).is_err(), + verify_module(module).is_ok(), "{}", format_assert_msg(module) ); @@ -328,8 +327,7 @@ fn nested_template_vector_param() { public foo(loc0: vector>, loc1: &mut TxContext) { } - it's a valid entry function and verification should FAIL due to - missing Key abilty on the nested vector's generic type + it's a valid entry function. It is assumed loc0 will be primitives, not objects */ let (mut builder, _) = ModuleBuilder::default(); @@ -350,7 +348,7 @@ fn nested_template_vector_param() { let module = builder.get_module(); assert!( - verify_module(module).is_err(), + verify_module(module).is_ok(), "{}", format_assert_msg(module) ); diff --git a/sui_types/src/error.rs b/sui_types/src/error.rs index 0b8307931b10a..f2b4910a304da 100644 --- a/sui_types/src/error.rs +++ b/sui_types/src/error.rs @@ -7,7 +7,7 @@ use thiserror::Error; use typed_store::rocks::TypedStoreError; use crate::base_types::*; -use move_binary_format::errors::PartialVMError; +use move_binary_format::errors::{PartialVMError, VMError}; use serde::{Deserialize, Serialize}; #[macro_export] @@ -282,6 +282,7 @@ pub enum SuiError { pub type SuiResult = Result; +// TODO these are both horribly wrong, categorization needs to be considered impl std::convert::From for SuiError { fn from(error: PartialVMError) -> Self { SuiError::ModuleVerificationFailure { @@ -289,3 +290,11 @@ impl std::convert::From for SuiError { } } } + +impl std::convert::From for SuiError { + fn from(error: VMError) -> Self { + SuiError::ModuleVerificationFailure { + error: error.to_string(), + } + } +} diff --git a/sui_types/src/move_package.rs b/sui_types/src/move_package.rs index ae5bded3e7b0f..5bf3364251113 100644 --- a/sui_types/src/move_package.rs +++ b/sui_types/src/move_package.rs @@ -2,86 +2,33 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - base_types::{ObjectID, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME}, - error::SuiError, - object::{Data, Object}, - SUI_FRAMEWORK_ADDRESS, -}; -use move_binary_format::{ - file_format::Visibility, - normalized::{Function, Type}, - CompiledModule, -}; -use move_core_types::{ - ident_str, - identifier::{IdentStr, Identifier}, - language_storage::{ModuleId, StructTag, TypeTag}, + base_types::ObjectID, + error::{SuiError, SuiResult}, }; +use move_binary_format::file_format::CompiledModule; +use move_core_types::identifier::Identifier; use serde::{Deserialize, Serialize}; use serde_bytes::ByteBuf; -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, - u32, -}; +use std::collections::BTreeMap; // TODO: robust MovePackage tests // #[cfg(test)] // #[path = "unit_tests/move_package.rs"] // mod base_types_tests; -pub const INIT_FN_NAME: &IdentStr = ident_str!("init"); - -pub struct TypeCheckSuccess { - pub module_id: ModuleId, - pub args: Vec>, - pub by_value_objects: BTreeMap, - pub mutable_ref_objects: Vec, - pub return_types: Vec, // to validate return types after the call -} - -/// An inner structure used to cache expensive calls that -/// otherwise involve repeated deserialization. -#[derive(Debug, Clone, Default)] -pub struct PackageCache { - pub function_signature_cache: - Arc>>, -} - -impl PartialEq for PackageCache { - fn eq(&self, _other: &Self) -> bool { - true - } -} -impl Eq for PackageCache {} - -use std::hash::Hash; -use std::hash::Hasher; - -impl Hash for PackageCache { - fn hash(&self, _state: &mut H) {} -} - // serde_bytes::ByteBuf is an analog of Vec with built-in fast serialization. #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] pub struct MovePackage { id: ObjectID, + // TODO use session cache module_map: BTreeMap, - - #[serde(skip)] - cache: PackageCache, } impl MovePackage { - pub fn serialized_module_map(&self) -> &BTreeMap { - &self.module_map - } - - pub fn from_map(id: ObjectID, module_map: &BTreeMap) -> Self { + pub fn new(id: ObjectID, module_map: &BTreeMap) -> Self { Self { id, module_map: module_map.clone(), - cache: PackageCache::default(), } } @@ -89,93 +36,20 @@ impl MovePackage { self.id } - pub fn module_id(&self, module: &Identifier) -> Result { - Ok(ModuleId::new(*self.id, module.clone())) + pub fn serialized_module_map(&self) -> &BTreeMap { + &self.module_map } - /// Get the function signature for the specified function - pub fn get_function_signature( - &self, - module: &Identifier, - function: &Identifier, - ) -> Result { - if let Some(func) = self - .cache - .function_signature_cache - .read() - .get(&(module.clone(), function.clone())) - { - return Ok(func.clone()); - } - + pub fn deserialize_module(&self, module: &Identifier) -> SuiResult { + // TODO use the session's cache let bytes = self .serialized_module_map() .get(module.as_str()) .ok_or_else(|| SuiError::ModuleNotFound { module_name: module.to_string(), })?; - let m = CompiledModule::deserialize(bytes) - .expect("Unwrap safe because Sui serializes/verifies modules before publishing them"); - - let func = - Function::new_from_name(&m, function).ok_or_else(|| SuiError::FunctionNotFound { - error: format!( - "Could not resolve function '{}' in module {}::{}", - function, - self.id(), - module - ), - })?; - - self.cache - .function_signature_cache - .write() - .insert((module.clone(), function.clone()), func.clone()); - - Ok(func) - } - - /// Checks if the specified function is an entry function and returns the function if so - /// There are specific rules for what can be an entry functions - /// If not entry functions, it returns Err - pub fn check_and_get_entry_function( - &self, - module: &Identifier, - function: &Identifier, - ) -> Result { - let function_signature = self.get_function_signature(module, function)?; - - // Function has to be public - if function_signature.visibility != Visibility::Public { - return Err(SuiError::InvalidFunctionSignature { - error: "Invoked function must have public visibility".to_string(), - }); - } - - // Entry function can only return primitive values - for (idx, r) in function_signature.return_.iter().enumerate() { - if !is_entry_ret_type(r) { - return Err(SuiError::InvalidFunctionSignature { - error: format!( - "Expected return value at index ({}) to be of a primitive type", - idx - ), - }); - } - } - - // Last arg must be `&mut TxContext` - let last_param = &function_signature.parameters[function_signature.parameters.len() - 1]; - if !is_param_tx_context(last_param) { - return Err(SuiError::InvalidFunctionSignature { - error: format!( - "Expected last parameter of function signature to be &mut {}::{}::{}, but found {}", - SUI_FRAMEWORK_ADDRESS, TX_CONTEXT_MODULE_NAME, TX_CONTEXT_STRUCT_NAME, last_param - ), - }); - } - - Ok(function_signature) + Ok(CompiledModule::deserialize(bytes) + .expect("Unwrap safe because Sui serializes/verifies modules before publishing them")) } } @@ -183,7 +57,7 @@ impl From<&Vec> for MovePackage { fn from(compiled_modules: &Vec) -> Self { let id = ObjectID::from(*compiled_modules[0].self_id().address()); - MovePackage::from_map( + Self::new( id, &compiled_modules .iter() @@ -196,239 +70,3 @@ impl From<&Vec> for MovePackage { ) } } - -/// - Check that `package_object`, `module` and `function` are valid -/// - Check that the the signature of `function` is well-typed w.r.t `type_args`, `object_args`, and `pure_args` -/// - Return the ID of the resolved module, a vector of BCS encoded arguments to pass to the VM, and a partitioning -/// of the input objects into objects passed by value vs by mutable reference -pub fn resolve_and_type_check( - package_object: &Object, - module: &Identifier, - function: &Identifier, - type_args: &[TypeTag], - object_args: Vec, - mut pure_args: Vec>, -) -> Result { - // Resolve the function we are calling - let (function_signature, module_id) = match &package_object.data { - Data::Package(package) => ( - package.check_and_get_entry_function(module, function)?, - package.module_id(module)?, - ), - Data::Move(_) => { - return Err(SuiError::ModuleLoadFailure { - error: "Expected a module object, but found a Move object".to_string(), - }) - } - }; - - // check arity of type and value arguments - if function_signature.type_parameters.len() != type_args.len() { - return Err(SuiError::InvalidFunctionSignature { - error: format!( - "Expected {:?} type arguments, but found {:?}", - function_signature.type_parameters.len(), - type_args.len() - ), - }); - } - - // total number of args is |objects| + |pure_args| + 1 for the the `TxContext` object - let num_args = object_args.len() + pure_args.len() + 1; - if function_signature.parameters.len() != num_args { - return Err(SuiError::InvalidFunctionSignature { - error: format!( - "Expected {:?} arguments calling function '{}', but found {:?}", - function_signature.parameters.len(), - function, - num_args - ), - }); - } - - // type check object arguments passed in by value and by reference - let mut args = Vec::new(); - let mut mutable_ref_objects = Vec::new(); - let mut by_value_objects = BTreeMap::new(); - #[cfg(debug_assertions)] - let mut num_immutable_objects = 0; - #[cfg(debug_assertions)] - let num_objects = object_args.len(); - - let ty_args: Vec = type_args.iter().map(|t| Type::from(t.clone())).collect(); - for (idx, object) in object_args.into_iter().enumerate() { - let mut param_type = function_signature.parameters[idx].clone(); - if !param_type.is_closed() { - param_type = param_type.subst(&ty_args); - } - match &object.data { - Data::Move(m) => { - args.push(m.contents().to_vec()); - // check that m.type_ matches the parameter types of the function - match ¶m_type { - Type::MutableReference(inner_t) => { - if object.is_read_only() { - return Err(SuiError::TypeError { - error: format!( - "Argument {} is expected to be mutable, immutable object found", - idx - ), - }); - } - type_check_struct(&m.type_, inner_t)?; - mutable_ref_objects.push(object); - } - Type::Reference(inner_t) => { - type_check_struct(&m.type_, inner_t)?; - #[cfg(debug_assertions)] - { - num_immutable_objects += 1 - } - } - Type::Struct { .. } => { - if object.is_shared() { - // Forbid passing shared (both mutable and immutable) object by value. - // This ensures that shared object cannot be transferred, deleted or wrapped. - return Err(SuiError::TypeError { - error: format!( - "Shared object cannot be passed by-value, found in argument {}", - idx - ), - }); - } - type_check_struct(&m.type_, ¶m_type)?; - let res = by_value_objects.insert(object.id(), object); - // should always pass due to earlier "no duplicate ID's" check - debug_assert!(res.is_none()) - } - t => { - return Err(SuiError::TypeError { - error: format!( - "Found object argument {}, but function expects {}", - m.type_, t - ), - }) - } - } - } - Data::Package(_) => { - return Err(SuiError::TypeError { - error: format!("Found module argument, but function expects {param_type}"), - }) - } - } - } - #[cfg(debug_assertions)] - debug_assert!( - by_value_objects.len() + mutable_ref_objects.len() + num_immutable_objects == num_objects - ); - // check that the non-object parameters are primitive types - for param_type in - &function_signature.parameters[args.len()..function_signature.parameters.len() - 1] - { - if !is_primitive(param_type) { - return Err(SuiError::TypeError { - error: format!("Expected primitive type, but found {param_type}"), - }); - } - } - args.append(&mut pure_args); - - Ok(TypeCheckSuccess { - module_id, - args, - by_value_objects, - mutable_ref_objects, - return_types: function_signature.return_, - }) -} - -/// Checks for the special TxContext param -pub fn is_param_tx_context(param: &Type) -> bool { - if let Type::MutableReference(typ) = param { - match &**typ { - Type::Struct { - address, - module, - name, - .. - } if address == &SUI_FRAMEWORK_ADDRESS - && module.as_ident_str() == TX_CONTEXT_MODULE_NAME - && name.as_ident_str() == TX_CONTEXT_STRUCT_NAME => - { - return true - } - _ => return false, - } - } - false -} - -// TODO: upstream Type::is_primitive in diem -pub fn is_primitive(t: &Type) -> bool { - // nested vectors of primitive types are OK to arbitrary nesting - // level - is_primitive_internal(t, 0, u32::MAX) -} - -pub fn is_entry_ret_type(t: &Type) -> bool { - // allow vectors of vectors but no deeper nesting - is_primitive_internal(t, 0, 2) -} - -pub fn is_primitive_internal(t: &Type, depth: u32, max_depth: u32) -> bool { - use Type::*; - match t { - Bool | U8 | U64 | U128 | Address => true, - Vector(inner_t) => { - if depth < max_depth { - is_primitive_internal(inner_t, depth + 1, max_depth) - } else { - false - } - } - Signer | Struct { .. } | TypeParameter(_) | Reference(_) | MutableReference(_) => false, - } -} - -fn type_check_struct(arg_type: &StructTag, param_type: &Type) -> Result<(), SuiError> { - if let Some(param_struct_type) = param_type.clone().into_struct_tag() { - if arg_type != ¶m_struct_type { - Err(SuiError::TypeError { - error: format!( - "Expected argument of type {}, but found type {}", - param_struct_type, arg_type - ), - }) - } else { - Ok(()) - } - } else { - Err(SuiError::TypeError { - error: format!( - "Expected argument of type {}, but found struct type {}", - param_type, arg_type - ), - }) - } -} -/// Checks if this module has a conformant `init` -pub fn module_has_init(module: &CompiledModule) -> bool { - let function = match Function::new_from_name(module, INIT_FN_NAME) { - Some(v) => v, - None => return false, - }; - if function.visibility != Visibility::Private { - return false; - } - if !function.type_parameters.is_empty() { - return false; - } - if !function.return_.is_empty() { - return false; - } - if function.parameters.len() != 1 { - return false; - } - is_param_tx_context(&function.parameters[0]) -}