Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ibc testing framework #165

Merged
merged 4 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ test-storage:
test-e2e:
cargo test -p e2e-move-tests --features testing

test-unit:
cargo test stdlib_move_unit_tests --features testing -p e2e-move-tests

build: precompile build-rust build-go

build-rust: build-rust-release
Expand Down
3 changes: 3 additions & 0 deletions crates/e2e-move-tests/src/tests/move_unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use move_cli::base::test::{run_move_unit_tests_with_factory, UnitTestResult};
use move_core_types::effects::ChangeSet;
use move_unit_test::UnitTestingConfig;
use move_vm_runtime::native_extensions::NativeContextExtensions;
use move_model::metadata::{CompilerVersion, LanguageVersion};

use once_cell::sync::Lazy;
use std::path::PathBuf;
Expand Down Expand Up @@ -69,6 +70,8 @@ fn run_tests_for_pkg(path_to_pkg: impl Into<String>) {
.compiler_config
.known_attributes
.clone_from(metadata::get_all_attribute_names());
build_config.compiler_config.compiler_version = Some(CompilerVersion::V2_1);
build_config.compiler_config.language_version = Some(LanguageVersion::V2_1);

let res = run_move_unit_tests_with_factory(
&pkg_path,
Expand Down
35 changes: 31 additions & 4 deletions crates/natives/src/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ fn native_requested_messages(
ty_args: Vec<Type>,
_arguments: VecDeque<Value>,
) -> SafeNativeResult<SmallVec<[Value; 1]>> {
let gas_params = &context.native_gas_params.initia_stdlib;
context.charge(gas_params.cosmos_stargate_base)?;

debug_assert!(ty_args.is_empty());
debug_assert!(_arguments.is_empty());

Expand All @@ -100,7 +97,37 @@ fn native_requested_messages(
.into_iter()
.map(|m| Value::struct_(Struct::pack(vec![Value::vector_u8(m.data)])));

Ok(smallvec![Value::vector_for_testing_only(messages)])
let options = cosmos_context
.messages
.borrow()
.clone()
.into_iter()
.map(|m| {
let (callback_id, callback_fid) = if let Some(callback) = m.callback {
(
callback.id,
format!(
"{}::{}::{}",
callback.module_address.to_standard_string(),
callback.module_name,
callback.function_name
)
.into_bytes(),
)
} else {
(0, vec![])
};
Value::struct_(Struct::pack(vec![
Value::bool(m.allow_failure),
Value::u64(callback_id),
Value::vector_u8(callback_fid),
]))
});

Ok(smallvec![
Value::vector_for_testing_only(messages),
Value::vector_for_testing_only(options)
])
}

/***************************************************************************************************
Expand Down
22 changes: 22 additions & 0 deletions crates/natives/src/ibctesting.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::dispatchable_fungible_asset::native_dispatch;
use crate::interface::{RawSafeNative, SafeNativeBuilder};
use move_vm_runtime::native_functions::NativeFunction;

/***************************************************************************************************
* module
*
**************************************************************************************************/
pub fn make_all(
builder: &SafeNativeBuilder,
) -> impl Iterator<Item = (String, NativeFunction)> + '_ {
let mut natives = vec![];

natives.extend([
("dispatchable_ibc_ack", native_dispatch as RawSafeNative),
("dispatchable_ibc_timeout", native_dispatch),
("dispatchable_callback", native_dispatch),
("dispatchable_on_receive", native_dispatch),
]);

builder.make_named_natives(natives)
}
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions crates/natives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ pub mod table;
pub mod transaction_context;
pub mod type_info;

#[cfg(feature = "testing")]
pub mod ibctesting;

use initia_move_gas::{MiscGasParameters, NativeGasParameters};
use interface::SafeNativeBuilder;
use move_core_types::account_address::AccountAddress;
Expand Down Expand Up @@ -83,6 +86,9 @@ pub fn initia_move_natives(
);
add_natives_from_module!("biguint", biguint::make_all(builder));

#[cfg(feature = "testing")]
add_natives_from_module!("ibctesting", ibctesting::make_all(builder));

make_table_from_iter(initia_std_addr, natives)
}

Expand Down
Binary file modified precompile/binaries/minlib/cosmos.mv
Binary file not shown.
Binary file modified precompile/binaries/minlib/json.mv
Binary file not shown.
Binary file modified precompile/binaries/stdlib/cosmos.mv
Binary file not shown.
Binary file modified precompile/binaries/stdlib/json.mv
Binary file not shown.
1 change: 1 addition & 0 deletions precompile/modules/initia_stdlib/Move.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ MoveNursery = { local = "../move_nursery" }
std = "0x1"
initia_std = "0x1"
relayer = "0x3d18d54532fc42e567090852db6eb21fa528f952"
cafe = "0xcafe"
2 changes: 1 addition & 1 deletion precompile/modules/initia_stdlib/sources/address.move
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ module initia_std::address {
assert!(addr == from_sdk(addr_sdk), 0)
}

// string <> address
// hex string <> address
native public fun to_string(addr: address): String;
native public fun from_string(addr_str: String): address;

Expand Down
55 changes: 53 additions & 2 deletions precompile/modules/initia_stdlib/sources/cosmos.move
Original file line number Diff line number Diff line change
Expand Up @@ -414,17 +414,30 @@ module initia_std::cosmos {
)
}

//
// Native Functions
//

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

#[test_only]
native public fun requested_messages(): vector<String>;
native public fun requested_messages(): (vector<String>, vector<Options>);

#[test_only]
public fun was_message_requested(msg: &String): bool {
was_message_requested_with_options(msg, &disallow_failure())
}

#[test_only]
public fun was_message_requested_with_options(
msg: &String, opt: &Options
): bool {
use std::vector;
vector::contains(&requested_messages(), msg)
let (messages, opts) = requested_messages();
let (found, idx) = vector::index_of(&messages, msg);
found && vector::borrow(&opts, idx) == opt
}

// ================================================== Options =================================================
Expand Down Expand Up @@ -493,6 +506,11 @@ module initia_std::cosmos {
}
}

/// Unpack options for external use
public fun unpack_options(opt: Options): (bool, u64, String) {
(opt.allow_failure, opt.callback_id, string::utf8(opt.callback_fid))
}

//=========================================== Tests ===========================================

#[test(sender = @0xcafe)]
Expand All @@ -518,4 +536,37 @@ module initia_std::cosmos {

assert!(was_message_requested(&msg), 1);
}

#[test(sender = @0xcafe)]
public fun test_stargate_with_options(sender: &signer) {
use std::string::{utf8, bytes};

let voter = utf8(b"voter");
let proposal_id = 1;
let option = 1;
let metadata = utf8(b"metadata");
let msg =
json::marshal_to_string(
&VoteRequest {
_type_: utf8(b"/cosmos.gov.v1.MsgVote"),
proposal_id,
voter: voter,
option,
metadata: metadata
}
);

stargate_with_options(
sender,
*bytes(&msg),
allow_failure_with_callback(1, utf8(b"0x1::test::test_fn"))
);

assert!(
was_message_requested_with_options(
&msg, &allow_failure_with_callback(1, utf8(b"0x1::test::test_fn"))
),
1
);
}
}
19 changes: 19 additions & 0 deletions precompile/modules/initia_stdlib/sources/function_info.move
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,23 @@ module initia_std::function_info {
// Test only dependencies so we can invoke those friend functions.
#[test_only]
friend initia_std::function_info_tests;

#[test_only]
public fun load_module_from_function_for_testing(f: &FunctionInfo) {
load_module_from_function(f)
}

#[test_only]
public fun new_function_info_for_testing(
module_address: address, module_name: String, function_name: String
): FunctionInfo {
new_function_info_from_address(module_address, module_name, function_name)
}

#[test_only]
public fun check_dispatch_type_compatibility_for_testing(
framework_function: &FunctionInfo, dispatch_target: &FunctionInfo
): bool {
check_dispatch_type_compatibility(framework_function, dispatch_target)
}
}
53 changes: 53 additions & 0 deletions precompile/modules/initia_stdlib/sources/ibctesting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# IBC Testing

This package provides convenient unit-test tools for IBC operations.

## How to Use

There are three steps in IBC testing:

### `execute_cosmos_messages()`

This function checks pending Cosmos messages and verifies if a message can be executed.

- If a message is executable, it transfers the requested token from the sender to the `@std` address.
- Otherwise, it performs no operation.

If a message is registered with options at `cosmos::stargate_with_options`, it will check and execute the given callback function if option contains callback. There are four cases for these options excluding callback options:

| Success | Option (allow_failure) | Abort | Revert |
|---------|-------------------------|-------|--------|
| Failed | true | false | true |
| Failed | false | true | true |
| True | true | false | false |
| True | false | false | false |

Refer to [ibc_transfer_tests.move](../../tests/ibc_transfer_tests.move) and [ibc_transfer_tests_helpers.move#95](../../tests/ibc_transfer_tests_helpers.move#95) for details.
beer-1 marked this conversation as resolved.
Show resolved Hide resolved

### `relay_packets()`

If there is at least one executable IBC transfer from the previous step, it will simulate IBC packet relaying by depositing the counterparty token to the recipient and executing the `on_receive` dispatch function.

The `on_receive` function should have the following signature:

```rust
public fun on_receive(recipient: &signer, msg_opt: &Option<ibctesting::MoveMessage>): bool
```

In the `on_receive` function, you can verify the passed message and ensure the counterparty token is correctly received. Refer to [ibc_transfer_tests_helpers.move#115](../../tests/ibc_transfer_tests_helpers.move#115) for details.

Based on the response of `on_receive`, the IBC packet relaying actions decide whether to execute acknowledgment with success or failure. Refer to [ibc_transfer_tests.move#222](../../tests/ibc_transfer_tests.move#222) for details.

You can also use `block::set_block_info` to simulate timeout cases. Refer to [ibc_transfer_tests.move#264](../../tests/ibc_transfer_tests.move#264) for details.

### `relay_acks_timeouts()`

Initia provides an async callback feature to allow a dApp developer to receive IBC packet success notifications. For more details, see [Initia IBC Hooks](https://github.com/initia-labs/initia/tree/main/x/ibc-hooks/move-hooks).

If a message contains a memo field like `{"move": {"message": {}, "async_callback": {"id": "100", "module_address": "0xabc", "module_name": "testing"}}}`, it will execute `0xabc::testing::ibc_ack` or `0xabc::testing::ibc_timeout` according to the result of IBC packet relaying.

## Avoid Re-entrancy in Unit Tests

The IBC testing package is built with the dispatch function of Aptos Move, which does not allow re-entrancy. When writing testing scripts, ensure that you do not call `ibctesting` or test modules from callback functions (`on_callback`, `on_receive`, `ibc_ack`, `ibc_timeout`).

To avoid re-entrancy issues, create a new test module, as demonstrated in [`ibc_transfer_tests`](../../tests/ibc_transfer_tests.move).
Loading
Loading