Skip to content

Commit

Permalink
feat: stargate options (#136)
Browse files Browse the repository at this point in the history
* fix compatibility

* introduce stargate optionsg

* formatting

* fix lint

* update comments
  • Loading branch information
beer-1 authored Oct 3, 2024
1 parent c533823 commit 09c8863
Show file tree
Hide file tree
Showing 13 changed files with 421 additions and 16 deletions.
11 changes: 11 additions & 0 deletions crates/e2e-move-tests/src/tests/cosmos.data/stargate/Move.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "test"
version = "0.0.0"

[dependencies]
MoveNursery = { local = "../../../../../../precompile/modules/move_nursery" }
InitiaStdlib = { local = "../../../../../../precompile/modules/initia_stdlib" }

[addresses]
initia_std = "0x1"
std = "0x1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module 0xCAFE::test {
use initia_std::string::String;
use initia_std::cosmos;

public entry fun stargate(sender: &signer, data: vector<u8>, allow_failure: bool, id: u64, fid: String) {
let options = if (allow_failure) {
cosmos::allow_failure_with_callback(id, fid)
} else {
cosmos::disallow_failure_with_callback(id, fid)
};

cosmos::stargate_with_options(sender, data, options);
}
}
39 changes: 38 additions & 1 deletion crates/e2e-move-tests/src/tests/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::tests::common::ExpectedOutput;
use crate::MoveHarness;
use initia_move_types::cosmos::{
CosmosCoin, CosmosMessage, DistributionMessage, IBCFee, IBCHeight, IBCMessage, MoveMessage,
StakingMessage, StargateMessage,
StakingMessage, StargateCallback, StargateMessage,
};
use move_core_types::account_address::AccountAddress;
use move_core_types::language_storage::TypeTag;
Expand Down Expand Up @@ -55,10 +55,16 @@ type TestInput<'a> = (
);

fn run_tests(tests: Vec<TestInput>) {
let acc = AccountAddress::from_hex_literal("0xcafe").expect("0xcafe account should be created");
let path = "src/tests/cosmos.data/stargate";
let mut h = MoveHarness::new();

h.initialize();

// publish package
let output = h.publish_package(&acc, path).expect("should success");
h.commit(output, true);

for (sender, entry, ty_args, args, exp_output) in tests {
let vm_output = h.run_entry_function(
vec![sender],
Expand Down Expand Up @@ -595,6 +601,37 @@ fn test_cosmos_stargate() {
Some(vec![CosmosMessage::Stargate(StargateMessage {
sender,
data: data.as_bytes().to_vec(),
allow_failure: false,
callback: None,
})]),
),
);
tests.push(test_stargate);

let test_stargate = (
sender,
"0xcafe::test::stargate",
vec![],
vec![
bcs::to_bytes(data.as_bytes()).unwrap(),
bcs::to_bytes(&false).unwrap(),
bcs::to_bytes(&123u64).unwrap(),
bcs::to_bytes("0xcafe::test::callback").unwrap(),
],
ExpectedOutput::new(
VMStatus::Executed,
None,
None,
Some(vec![CosmosMessage::Stargate(StargateMessage {
sender,
data: data.as_bytes().to_vec(),
allow_failure: false,
callback: Some(StargateCallback {
id: 123,
module_address: AccountAddress::from_hex_literal("0xcafe").unwrap(),
module_name: "test".to_string(),
function_name: "callback".to_string(),
}),
})]),
),
);
Expand Down
25 changes: 21 additions & 4 deletions crates/natives/src/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ use move_core_types::{account_address::AccountAddress, gas_algebra::NumBytes};
use move_vm_runtime::native_functions::NativeFunction;
use move_vm_types::{
loaded_data::runtime_types::Type,
values::{StructRef, Value, Vector},
values::{Struct, StructRef, Value, Vector},
};
use smallvec::{smallvec, SmallVec};
use std::{cell::RefCell, collections::VecDeque};

use crate::{
helpers::{get_metadata_address, partial_extension_error},
helpers::{get_metadata_address, get_stargate_options, partial_extension_error},
interface::{
RawSafeNative, SafeNativeBuilder, SafeNativeContext, SafeNativeError, SafeNativeResult,
},
Expand Down Expand Up @@ -54,13 +54,30 @@ fn native_stargate(
context.charge(gas_params.cosmos_stargate_base)?;

debug_assert!(ty_args.is_empty());
debug_assert!(arguments.len() == 2);
debug_assert!(arguments.len() == 3);

let (allow_failure, callback) = get_stargate_options(safely_pop_arg!(arguments, Struct))?;
if callback.is_some() {
let callback = callback.as_ref().unwrap();
context.charge(
gas_params.cosmos_stargate_per_byte * NumBytes::new(callback.module_name.len() as u64),
)?;
context.charge(
gas_params.cosmos_stargate_per_byte
* NumBytes::new(callback.function_name.len() as u64),
)?;
}

let data = safely_pop_arg!(arguments, Vector).to_vec_u8()?;
context.charge(gas_params.cosmos_stargate_per_byte * NumBytes::new(data.len() as u64))?;

let sender: AccountAddress = safely_pop_arg!(arguments, AccountAddress);
let message = CosmosMessage::Stargate(StargateMessage { sender, data });
let message = CosmosMessage::Stargate(StargateMessage {
sender,
data,
callback,
allow_failure,
});

// build cosmos message
let cosmos_context = context.extensions().get::<NativeCosmosContext>();
Expand Down
64 changes: 57 additions & 7 deletions crates/natives/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::str::from_utf8;

use initia_move_types::cosmos::StargateCallback;
use move_binary_format::errors::{PartialVMError, PartialVMResult};
use move_core_types::{account_address::AccountAddress, vm_status::StatusCode};
use move_vm_types::values::{Reference, Struct, StructRef, Value};
use move_vm_types::values::{Reference, Struct, StructRef, Value, Vector};

// =========================================================================================
// Helpers
Expand All @@ -19,18 +22,65 @@ pub fn get_metadata_address(metadata: &StructRef) -> PartialVMResult<AccountAddr
pub fn get_string(v: Struct) -> PartialVMResult<Vec<u8>> {
let mut vals: Vec<Value> = v
.unpack()
.map_err(|_| {
PartialVMError::new(StatusCode::VM_EXTENSION_ERROR)
.with_message("failed to deserialize arg".to_string())
})?
.map_err(|_| partial_extension_error("failed to deserialize arg"))?
.collect();
vals.pop().map_or(
Err(PartialVMError::new(StatusCode::VM_EXTENSION_ERROR)
.with_message("failed to deserialize arg".to_string())),
Err(partial_extension_error("failed to deserialize arg")),
|v| v.value_as::<Vec<u8>>(),
)
}

pub fn get_stargate_options(v: Struct) -> PartialVMResult<(bool, Option<StargateCallback>)> {
let mut vals: Vec<Value> = v
.unpack()
.map_err(|_| partial_extension_error("failed to deserialize arg"))?
.collect();

let callback_fid = vals
.pop()
.map_or(Err(partial_extension_error("invalid callback_fid")), |v| {
v.value_as::<Vector>()
})?
.to_vec_u8()?;
let callback_id = vals
.pop()
.map_or(Err(partial_extension_error("invalid callback_id")), |v| {
v.value_as::<u64>()
})?;
let allow_failure = vals.pop().map_or(
Err(partial_extension_error("failed to deserialize arg")),
|v| v.value_as::<bool>(),
)?;

if callback_id == 0 {
Ok((allow_failure, None))
} else {
let callback_fid = from_utf8(&callback_fid)
.map_err(|_| partial_extension_error("invalid callback_fid"))?;
let mut callback_fid = callback_fid.splitn(3, "::").collect::<Vec<&str>>();
if callback_fid.len() != 3 {
return Err(partial_extension_error("malformed callback_fid"));
}

let fname = callback_fid.pop().unwrap().to_string();
let mname = callback_fid.pop().unwrap().to_string();
let maddr = callback_fid.pop().unwrap();

let maddr = AccountAddress::from_hex_literal(maddr)
.map_err(|_| partial_extension_error("invalid address in callback_fid"))?;

Ok((
allow_failure,
Some(StargateCallback {
id: callback_id,
module_address: maddr,
module_name: mname,
function_name: fname,
}),
))
}
}

pub fn partial_extension_error(msg: impl ToString) -> PartialVMError {
PartialVMError::new(StatusCode::VM_EXTENSION_ERROR).with_message(msg.to_string())
}
10 changes: 10 additions & 0 deletions crates/types/src/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ pub enum CosmosMessage {
pub struct StargateMessage {
pub sender: AccountAddress,
pub data: Vec<u8>,
pub allow_failure: bool,
pub callback: Option<StargateCallback>,
}

#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
pub struct StargateCallback {
pub id: u64,
pub module_address: AccountAddress,
pub module_name: String,
pub function_name: String,
}

#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
Expand Down
Binary file modified precompile/binaries/minlib/cosmos.mv
Binary file not shown.
Binary file modified precompile/binaries/stdlib/cosmos.mv
Binary file not shown.
1 change: 1 addition & 0 deletions precompile/modules/initia_stdlib/sources/account.move
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ module initia_std::account {

#[test_only]
public fun create_account_for_test(new_address: address): signer {
create_account(new_address);
create_signer_for_test(new_address)
}
}
97 changes: 95 additions & 2 deletions precompile/modules/initia_stdlib/sources/cosmos.move
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ module initia_std::cosmos {
use std::object::Object;
use std::fungible_asset::Metadata;
use std::collection::{Collection};
use std::error;

use initia_std::json;

// Error codes
const EINVALID_CALLBACK_ID: u64 = 1;
const EINVALID_CALLBACK_FID: u64 = 2;

struct VoteRequest has copy, drop {
_type_: String,
proposal_id: u64,
Expand Down Expand Up @@ -40,7 +45,27 @@ module initia_std::cosmos {
}

public entry fun stargate(sender: &signer, data: vector<u8>) {
stargate_internal(signer::address_of(sender), data)
stargate_internal(signer::address_of(sender), data, disallow_failure())
}

/// Stargate message with options
///
/// Options:
/// - allow_failure()
/// - disallow_failure()
/// - allow_failure_with_callback(id: u64, fid: String)
/// - disallow_failure_with_callback(id: u64, fid: String)
///
/// The callback function should be defined with the following signature:
/// ```rust
/// public fun callback(id: u64, success: bool);
/// public fun callback(sender: &signer, id: u64, success: bool);
/// ```
///
public fun stargate_with_options(
sender: &signer, data: vector<u8>, options: Options
) {
stargate_internal(signer::address_of(sender), data, options)
}

public entry fun move_execute(
Expand Down Expand Up @@ -213,7 +238,9 @@ module initia_std::cosmos {
)
}

native fun stargate_internal(sender: address, data: vector<u8>);
native fun stargate_internal(
sender: address, data: vector<u8>, option: Options
);

native fun move_execute_internal(
sender: address,
Expand Down Expand Up @@ -281,4 +308,70 @@ module initia_std::cosmos {
timeout_fee_metadata: &Object<Metadata>,
timeout_fee_amount: u64
);

// ================================================== Options =================================================

/// Options for stargate message
struct Options has copy, drop {
allow_failure: bool,

/// callback_id is the unique identifier for this message execution.
callback_id: u64,
/// function identifier which will be called after the message execution.
/// The function should be defined with the following signature:
/// ```rust
/// public fun callback(id: u64, success: bool);
/// public fun callback(sender: &signer, id: u64, success: bool);
/// ```
///
/// Ex) 0xaddr::test_module::callback
/// where callback is the function name defined in the test_module of the 0xaddr address.
callback_fid: vector<u8>
}

public fun allow_failure(): Options {
Options {
allow_failure: true,
callback_id: 0,
callback_fid: vector::empty()
}
}

public fun disallow_failure(): Options {
Options {
allow_failure: false,
callback_id: 0,
callback_fid: vector::empty()
}
}

/// Ex) fid: 0xaddr::test_module::callback
/// where callback is the function name defined in the test_module of the 0xaddr address.
public fun allow_failure_with_callback(id: u64, fid: String): Options {
assert!(id > 0, error::invalid_argument(EINVALID_CALLBACK_ID));
assert!(
!string::is_empty(&fid), error::invalid_argument(EINVALID_CALLBACK_FID)
);

Options {
allow_failure: true,
callback_id: id,
callback_fid: *string::bytes(&fid)
}
}

/// Ex) fid: 0xaddr::test_module::callback
/// where callback is the function name defined in the test_module of the 0xaddr address.
public fun disallow_failure_with_callback(id: u64, fid: String): Options {
assert!(id > 0, error::invalid_argument(EINVALID_CALLBACK_ID));
assert!(
!string::is_empty(&fid), error::invalid_argument(EINVALID_CALLBACK_FID)
);

Options {
allow_failure: false,
callback_id: id,
callback_fid: *string::bytes(&fid)
}
}
}
1 change: 1 addition & 0 deletions precompile/modules/minitia_stdlib/sources/account.move
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ module minitia_std::account {

#[test_only]
public fun create_account_for_test(new_address: address): signer {
create_account(new_address);
create_signer_for_test(new_address)
}
}
Loading

0 comments on commit 09c8863

Please sign in to comment.