diff --git a/.changelog/unreleased/features/4082-ibc-pfm.md b/.changelog/unreleased/features/4082-ibc-pfm.md new file mode 100644 index 0000000000..5aebd47ffc --- /dev/null +++ b/.changelog/unreleased/features/4082-ibc-pfm.md @@ -0,0 +1,3 @@ +- Implement compatibility with Strangelove's Packet Forward Middleware + in Namada, to allow forwarding ICS-20 packets over multiple chains. + ([\#4082](https://github.com/anoma/namada/pull/4082)) \ No newline at end of file diff --git a/.changelog/unreleased/features/4134-ibc-pfm-with-invalid-addrs.md b/.changelog/unreleased/features/4134-ibc-pfm-with-invalid-addrs.md new file mode 100644 index 0000000000..ab53b5da5a --- /dev/null +++ b/.changelog/unreleased/features/4134-ibc-pfm-with-invalid-addrs.md @@ -0,0 +1,2 @@ +- Disable validation of IBC ICS-20 receivers, while handling PFM packets. + ([\#4134](https://github.com/anoma/namada/pull/4134)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index 51ac256df2..3875ac070f 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -6,7 +6,11 @@ "e2e::ibc_tests::fee_payment_with_ibc_token": 357, "e2e::ibc_tests::ibc_token_inflation": 840, "e2e::ibc_tests::ibc_rate_limit": 485, + "e2e::ibc_tests::ibc_pfm_happy_flows": 485, + "e2e::ibc_tests::ibc_pfm_unhappy_flows": 485, "e2e::ibc_tests::ibc_upgrade_client": 280, + "e2e::ibc_tests::ibc_shielded_recv_middleware_happy_flow": 280, + "e2e::ibc_tests::ibc_shielded_recv_middleware_unhappy_flow": 280, "e2e::eth_bridge_tests::test_add_to_bridge_pool": 10, "e2e::ledger_tests::double_signing_gets_slashed": 12, "e2e::ledger_tests::ledger_many_txs_in_a_block": 55, diff --git a/Cargo.lock b/Cargo.lock index 33fba74f2d..253ecb64ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1885,6 +1885,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "dur" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5b6c91b5e394b75cd96c36393fc938496c030220207a0ccf34d6cd313d3b49" +dependencies = [ + "nom", + "rust_decimal", +] + [[package]] name = "duration-str" version = "0.10.0" @@ -3333,7 +3343,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-apps", "ibc-clients", @@ -3346,7 +3356,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer-types", "ibc-core", @@ -3356,7 +3366,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "base64 0.22.1", @@ -3378,7 +3388,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-transfer-types", "ibc-core", @@ -3388,7 +3398,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3407,7 +3417,7 @@ dependencies = [ [[package]] name = "ibc-apps" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer", "ibc-app-transfer", @@ -3416,7 +3426,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "ibc-client-tendermint-types", @@ -3433,7 +3443,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "displaydoc", "ibc-core-client-types", @@ -3450,7 +3460,7 @@ dependencies = [ [[package]] name = "ibc-client-wasm-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "base64 0.22.1", "displaydoc", @@ -3464,7 +3474,7 @@ dependencies = [ [[package]] name = "ibc-clients" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-tendermint", "ibc-client-wasm-types", @@ -3473,7 +3483,7 @@ dependencies = [ [[package]] name = "ibc-core" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -3489,7 +3499,7 @@ dependencies = [ [[package]] name = "ibc-core-channel" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel-types", "ibc-core-client", @@ -3504,7 +3514,7 @@ dependencies = [ [[package]] name = "ibc-core-channel-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3528,7 +3538,7 @@ dependencies = [ [[package]] name = "ibc-core-client" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-client-context", "ibc-core-client-types", @@ -3541,7 +3551,7 @@ dependencies = [ [[package]] name = "ibc-core-client-context" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -3557,7 +3567,7 @@ dependencies = [ [[package]] name = "ibc-core-client-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3578,7 +3588,7 @@ dependencies = [ [[package]] name = "ibc-core-commitment-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3598,7 +3608,7 @@ dependencies = [ [[package]] name = "ibc-core-connection" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-wasm-types", "ibc-core-client", @@ -3612,7 +3622,7 @@ dependencies = [ [[package]] name = "ibc-core-connection-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3634,7 +3644,7 @@ dependencies = [ [[package]] name = "ibc-core-handler" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -3649,7 +3659,7 @@ dependencies = [ [[package]] name = "ibc-core-handler-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3674,7 +3684,7 @@ dependencies = [ [[package]] name = "ibc-core-host" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -3692,7 +3702,7 @@ dependencies = [ [[package]] name = "ibc-core-host-cosmos" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -3715,7 +3725,7 @@ dependencies = [ [[package]] name = "ibc-core-host-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3731,7 +3741,7 @@ dependencies = [ [[package]] name = "ibc-core-router" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -3745,7 +3755,7 @@ dependencies = [ [[package]] name = "ibc-core-router-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -3764,17 +3774,118 @@ dependencies = [ [[package]] name = "ibc-derive" version = "0.8.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "proc-macro2", "quote", "syn 2.0.52", ] +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-overflow-receive" +version = "0.4.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-app-transfer-types", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "ibc-middleware-packet-forward" +version = "0.9.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "borsh", + "dur", + "either", + "ibc-app-transfer-types", + "ibc-core-channel", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-primitives", + "serde", + "serde_json", +] + [[package]] name = "ibc-primitives" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "arbitrary", "borsh", @@ -3815,7 +3926,7 @@ dependencies = [ [[package]] name = "ibc-query" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "displaydoc", "ibc", @@ -3826,7 +3937,7 @@ dependencies = [ [[package]] name = "ibc-testkit" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "basecoin-store", "derive_more", @@ -5045,8 +5156,13 @@ dependencies = [ "assert_matches", "borsh", "data-encoding", + "dur", "ibc", "ibc-derive", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0)", + "ibc-middleware-overflow-receive", + "ibc-middleware-packet-forward", "ibc-testkit", "ics23", "konst", @@ -5273,6 +5389,7 @@ dependencies = [ "arbitrary", "assert_matches", "async-trait", + "bech32 0.8.1", "bimap", "borsh", "circular-queue", @@ -5476,12 +5593,14 @@ dependencies = [ "concat-idents", "data-encoding", "derivative", + "dur", "escargot", "expectrl", "eyre", "flate2", "fs_extra", "hyper 0.14.27", + "ibc-middleware-packet-forward", "ibc-testkit", "ics23", "itertools 0.12.1", @@ -6634,9 +6753,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -6825,9 +6944,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -7263,12 +7382,18 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", + "borsh", + "bytes", "num-traits 0.2.17", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", ] [[package]] @@ -7658,11 +7783,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index a9b41e08ff..e05f91213c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ derivation-path = "0.2.0" derivative = "2.2.0" directories = "4.0.1" drain_filter_polyfill = "0.1.3" +dur = "0.5.3" duration-str = "0.10.0" ed25519-consensus = "2.1.0" either = "1.12.0" @@ -117,10 +118,14 @@ flume = "0.11.1" fs_extra = "1.2.0" futures = "0.3" git2 = { version = "0.18.1", default-features = false } -# branch yuji/derive-arbitrary -ibc = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38bd2a32f35117d4d9165a3c68c64ccd87ad56dd", features = ["serde"] } -ibc-derive = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" } -ibc-testkit = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38bd2a32f35117d4d9165a3c68c64ccd87ad56dd", default-features = false } +# branch tiago/optional-ack +ibc = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38489943c4e75206eaffeeeec6153c039c2499d1", features = ["serde"] } +ibc-derive = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38489943c4e75206eaffeeeec6153c039c2499d1" } +ibc-middleware-module = { git = "https://github.com/heliaxdev/ibc-middleware", tag = "module/v0.1.0" } +ibc-middleware-module-macros = { git = "https://github.com/heliaxdev/ibc-middleware", tag = "module-macros/v0.1.0" } +ibc-middleware-overflow-receive = { git = "https://github.com/heliaxdev/ibc-middleware", tag = "orm/v0.4.0" } +ibc-middleware-packet-forward = { git = "https://github.com/heliaxdev/ibc-middleware", tag = "pfm/v0.9.0", features = ["borsh"] } +ibc-testkit = { git = "https://github.com/heliaxdev/cosmos-ibc-rs", rev = "38489943c4e75206eaffeeeec6153c039c2499d1", default-features = false } ics23 = "0.12.0" usize-set = { version = "0.10.3", features = ["serialize-borsh", "serialize-serde"] } indexmap = { git = "https://github.com/heliaxdev/indexmap", tag = "2.2.4-heliax-1", features = ["borsh-schema", "serde"] } @@ -173,7 +178,7 @@ rpassword = "5.0.1" rustversion = "1.0" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" -serde_json = "1.0.62" +serde_json = "1.0.133" serde_tuple = "0.5.0" sha2 = "0.9.3" sha2-const = "0.1.2" diff --git a/crates/apps/src/bin/namada/cli.rs b/crates/apps/src/bin/namada/cli.rs index f3165a4a41..bbe37d5959 100644 --- a/crates/apps/src/bin/namada/cli.rs +++ b/crates/apps/src/bin/namada/cli.rs @@ -50,6 +50,7 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Namada::TxShieldingTransfer(_) | cli::cmds::Namada::TxUnshieldingTransfer(_) | cli::cmds::Namada::TxIbcTransfer(_) + | cli::cmds::Namada::TxOsmosisSwap(_) | cli::cmds::Namada::TxUpdateAccount(_) | cli::cmds::Namada::TxRevealPk(_) | cli::cmds::Namada::TxInitProposal(_) diff --git a/crates/apps_lib/src/cli.rs b/crates/apps_lib/src/cli.rs index d9e50d1f89..9f00730359 100644 --- a/crates/apps_lib/src/cli.rs +++ b/crates/apps_lib/src/cli.rs @@ -63,6 +63,7 @@ pub mod cmds { TxShieldingTransfer(TxShieldingTransfer), TxUnshieldingTransfer(TxUnshieldingTransfer), TxIbcTransfer(TxIbcTransfer), + TxOsmosisSwap(TxOsmosisSwap), TxUpdateAccount(TxUpdateAccount), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), @@ -84,6 +85,7 @@ pub mod cmds { .subcommand(TxShieldingTransfer::def().display_order(2)) .subcommand(TxUnshieldingTransfer::def().display_order(2)) .subcommand(TxIbcTransfer::def().display_order(2)) + .subcommand(TxOsmosisSwap::def().display_order(2)) .subcommand(TxUpdateAccount::def().display_order(2)) .subcommand(TxInitProposal::def().display_order(2)) .subcommand(TxVoteProposal::def().display_order(2)) @@ -107,6 +109,8 @@ pub mod cmds { SubCmd::parse(matches).map(Self::TxUnshieldingTransfer); let tx_ibc_transfer = SubCmd::parse(matches).map(Self::TxIbcTransfer); + let tx_osmosis_swap = + SubCmd::parse(matches).map(Self::TxOsmosisSwap); let tx_update_account = SubCmd::parse(matches).map(Self::TxUpdateAccount); let tx_init_proposal = @@ -124,6 +128,7 @@ pub mod cmds { .or(tx_shielding_transfer) .or(tx_unshielding_transfer) .or(tx_ibc_transfer) + .or(tx_osmosis_swap) .or(tx_update_account) .or(tx_init_proposal) .or(tx_vote_proposal) @@ -239,6 +244,7 @@ pub mod cmds { .subcommand(TxShieldingTransfer::def().display_order(1)) .subcommand(TxUnshieldingTransfer::def().display_order(1)) .subcommand(TxIbcTransfer::def().display_order(1)) + .subcommand(TxOsmosisSwap::def().display_order(1)) .subcommand(TxUpdateAccount::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) .subcommand(TxRevealPk::def().display_order(1)) @@ -313,6 +319,7 @@ pub mod cmds { let tx_unshielding_transfer = Self::parse_with_ctx(matches, TxUnshieldingTransfer); let tx_ibc_transfer = Self::parse_with_ctx(matches, TxIbcTransfer); + let tx_osmosis_swap = Self::parse_with_ctx(matches, TxOsmosisSwap); let tx_update_account = Self::parse_with_ctx(matches, TxUpdateAccount); let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); @@ -401,6 +408,7 @@ pub mod cmds { .or(tx_shielding_transfer) .or(tx_unshielding_transfer) .or(tx_ibc_transfer) + .or(tx_osmosis_swap) .or(tx_update_account) .or(tx_init_account) .or(tx_reveal_pk) @@ -495,6 +503,7 @@ pub mod cmds { TxShieldingTransfer(TxShieldingTransfer), TxUnshieldingTransfer(TxUnshieldingTransfer), TxIbcTransfer(TxIbcTransfer), + TxOsmosisSwap(TxOsmosisSwap), QueryResult(QueryResult), TxUpdateAccount(TxUpdateAccount), TxInitAccount(TxInitAccount), @@ -1433,6 +1442,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxOsmosisSwap(pub args::TxOsmosisSwap); + + impl SubCmd for TxOsmosisSwap { + const CMD: &'static str = "osmosis-swap"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxOsmosisSwap(args::TxOsmosisSwap::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about(wrap!("Swap two asset kinds using Osmosis.")) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct TxUpdateAccount(pub args::TxUpdateAccount); @@ -3399,6 +3427,7 @@ pub mod args { use crate::wrap; pub const ADDRESS: Arg = arg("address"); + pub const ADDRESS_OPT: ArgOpt = arg_opt("address"); pub const ADD_PERSISTENT_PEERS: ArgFlag = flag("add-persistent-peers"); pub const ALIAS_OPT: ArgOpt = ALIAS.opt(); pub const ALIAS: Arg = arg("alias"); @@ -3551,6 +3580,7 @@ pub mod args { pub const LIST_FIND_ADDRESSES_ONLY: ArgFlag = flag("addr"); pub const LIST_FIND_KEYS_ONLY: ArgFlag = flag("keys"); pub const LOCALHOST: ArgFlag = flag("localhost"); + pub const LOCAL_RECOVERY_ADDR: Arg = arg("local-recovery-addr"); pub const MASP_EPOCH: ArgOpt = arg_opt("masp-epoch"); pub const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); @@ -3559,6 +3589,8 @@ pub mod args { pub const MAX_ETH_GAS: ArgOpt = arg_opt("max_eth-gas"); pub const MEMO_OPT: ArgOpt = arg_opt("memo"); pub const MIGRATION_PATH: ArgOpt = arg_opt("migration-path"); + pub const MINIMUM_AMOUNT: ArgOpt = + arg_opt("minimum-amount"); pub const MODE: ArgOpt = arg_opt("mode"); pub const NET_ADDRESS: Arg = arg("net-address"); pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); @@ -3567,13 +3599,19 @@ pub mod args { pub const NUT: ArgFlag = flag("nut"); pub const OUT_FILE_PATH_OPT: ArgOpt = arg_opt("out-file-path"); pub const OUTPUT: ArgOpt = arg_opt("output"); + pub const OUTPUT_DENOM: Arg = arg("output-denom"); pub const OUTPUT_FOLDER_PATH: ArgOpt = arg_opt("output-folder-path"); + pub const OSMOSIS_POOL_HOP: ArgMulti = + arg_multi("pool-hop"); + pub const OVERFLOW_OPT: ArgOpt = arg_opt("overflow-addr"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PATH: Arg = arg("path"); pub const PATH_OPT: ArgOpt = arg_opt("path"); pub const PAYMENT_ADDRESS_TARGET: Arg = arg("target"); + pub const PAYMENT_ADDRESS_TARGET_OPT: ArgOpt = + arg_opt("target-pa"); pub const PORT_ID: ArgDefault = arg_default( "port-id", DefaultFn(|| PortId::from_str("transfer").unwrap()), @@ -3621,6 +3659,7 @@ pub mod args { pub const SHIELDED: ArgFlag = flag("shielded"); pub const SHOW_IBC_TOKENS: ArgFlag = flag("show-ibc-tokens"); pub const SIGNER: ArgOpt = arg_opt("signer"); + pub const SLIPPAGE: ArgOpt = arg_opt("slippage-percentage"); pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); pub const SIGNATURES: ArgMulti = arg_multi("signatures"); @@ -3634,6 +3673,7 @@ pub mod args { pub const STORAGE_KEY: Arg = arg("storage-key"); pub const SUSPEND_ACTION: ArgFlag = flag("suspend"); pub const TARGET: Arg = arg("target"); + pub const TARGET_OPT: ArgOpt = arg_opt("target"); pub const TEMPLATES_PATH: Arg = arg("templates-path"); pub const TIMEOUT_HEIGHT: ArgOpt = arg_opt("timeout-height"); pub const TIMEOUT_SEC_OFFSET: ArgOpt = arg_opt("timeout-sec-offset"); @@ -3677,6 +3717,7 @@ pub mod args { pub const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); pub const WASM_DIR: ArgOpt = arg_opt("wasm-dir"); pub const WEBSITE_OPT: ArgOpt = arg_opt("website"); + pub const WINDOW_SECONDS: ArgOpt = arg_opt("window-seconds"); pub const WITH_INDEXER: ArgOpt = arg_opt("with-indexer"); pub const WRAPPER_SIGNATURE_OPT: ArgOpt = arg_opt("gas-signature"); pub const TX_PATH: Arg = arg("tx-path"); @@ -5106,6 +5147,164 @@ pub mod args { } } + impl CliToSdk> for TxOsmosisSwap { + type Error = std::io::Error; + + fn to_sdk( + self, + ctx: &mut Context, + ) -> Result, Self::Error> { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let recipient = match self.recipient { + Either::Left(r) => Either::Left(chain_ctx.get(&r)), + Either::Right(r) => Either::Right(chain_ctx.get(&r)), + }; + let overflow = self.overflow.map(|r| chain_ctx.get(&r)); + Ok(TxOsmosisSwap { + transfer: self.transfer.to_sdk(ctx)?, + output_denom: self.output_denom, + recipient, + overflow, + slippage: self.slippage, + local_recovery_addr: self.local_recovery_addr, + route: self.route, + }) + } + } + + impl Args for TxOsmosisSwap { + fn parse(matches: &ArgMatches) -> Self { + let transfer = TxIbcTransfer::parse(matches); + let output_denom = OUTPUT_DENOM.parse(matches); + let maybe_traans_recipient = TARGET_OPT.parse(matches); + let maybe_shielded_recipient = + PAYMENT_ADDRESS_TARGET_OPT.parse(matches); + let maybe_overflow = OVERFLOW_OPT.parse(matches); + let slippage_percent = SLIPPAGE.parse(matches); + if let Some(percent) = slippage_percent { + if percent > 100 { + panic!( + "The slippage percent must be an integer between 0 \ + and 100." + ) + } + } + let window_seconds = WINDOW_SECONDS.parse(matches); + let minimum_amount = MINIMUM_AMOUNT.parse(matches); + let slippage = minimum_amount + .map(|d| Slippage::MinOutputAmount(d.redenominate(0).amount())) + .or_else(|| { + Some(Slippage::Twap { + slippage_percentage: slippage_percent + .expect( + "If a minimum amount was not provided, \ + slippage-percent and window-seconds must be \ + specified.", + ) + .to_string(), + window_seconds: window_seconds.expect( + "If a minimum amount was not provided, \ + slippage-percent and window-seconds must be \ + specified.", + ), + }) + }) + .unwrap(); + let local_recovery_addr = LOCAL_RECOVERY_ADDR.parse(matches); + let route = match OSMOSIS_POOL_HOP.parse(matches) { + r if r.is_empty() => None, + r => Some(r), + }; + Self { + transfer, + output_denom, + recipient: if let Some(target) = maybe_traans_recipient { + Either::Left(target) + } else { + Either::Right(maybe_shielded_recipient.unwrap()) + }, + overflow: maybe_overflow, + slippage, + local_recovery_addr, + route, + } + } + + fn def(app: App) -> App { + app.add_args::>() + .arg(OSMOSIS_POOL_HOP.def().help(wrap!( + "Individual hop of the route to take through Osmosis \ + pools. This value takes the form \ + :. When unspecified, \ + the optimal route is queried on the fly." + ))) + .arg(OUTPUT_DENOM.def().help(wrap!( + "The IBC denomination (on Osmosis) of the desired asset." + ))) + .arg(TARGET_OPT.def().help(wrap!( + "The transparent Namada address that shall receive the \ + swapped tokens." + ))) + .arg( + PAYMENT_ADDRESS_TARGET_OPT + .def() + .requires(OVERFLOW_OPT.name) + .help(wrap!( + "The shielded Namada address that shall receive \ + the minimum amount of swapped tokens." + )), + ) + .arg(OVERFLOW_OPT.def().help(wrap!( + "The transparent address that receives the amount of \ + target asset exceeding the minimum amount. If used in \ + conjunction with shielding, this address should not be \ + linkable to any of your transparent accounts." + ))) + .arg(SLIPPAGE.def().requires(WINDOW_SECONDS.name).help(wrap!( + "The slippage percentage as an integer between 0 and 100. \ + Represents the maximum acceptable deviation from the \ + expected price during a trade." + ))) + .arg(WINDOW_SECONDS.def().requires(SLIPPAGE.name).help(wrap!( + "The time period (in seconds) over which the average \ + price is calculated." + ))) + .arg( + MINIMUM_AMOUNT + .def() + .conflicts_with(SLIPPAGE.name) + .conflicts_with(WINDOW_SECONDS.name) + .help(wrap!( + "The minimum amount of target asset that the \ + trade should produce." + )), + ) + .arg(LOCAL_RECOVERY_ADDR.def().help(wrap!( + "An address on Osmosis from which to recover funds in \ + case of failure." + ))) + .group( + ArgGroup::new("slippage") + .args([SLIPPAGE.name, MINIMUM_AMOUNT.name]) + .required(true), + ) + .group( + ArgGroup::new("transfer-target") + .args([ + TARGET_OPT.name, + PAYMENT_ADDRESS_TARGET_OPT.name, + ]) + .required(true), + ) + .mut_arg(RECEIVER.name, |arg| { + arg.long("swap-contract").help(wrap!( + "The address of the Osmosis contract performing the \ + swap. It will be the receiver of the IBC transfer." + )) + }) + } + } + impl CliToSdk> for TxInitAccount { type Error = std::io::Error; diff --git a/crates/apps_lib/src/cli/client.rs b/crates/apps_lib/src/cli/client.rs index 3727739cca..b56da488ec 100644 --- a/crates/apps_lib/src/cli/client.rs +++ b/crates/apps_lib/src/cli/client.rs @@ -113,6 +113,21 @@ impl CliApi { let namada = ctx.to_sdk(client, io); tx::submit_ibc_transfer(&namada, args).await?; } + Sub::TxOsmosisSwap(TxOsmosisSwap(args)) => { + let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let ledger_address = + chain_ctx.get(&args.transfer.tx.ledger_address); + let client = client.unwrap_or_else(|| { + C::from_tendermint_address(&ledger_address) + }); + client.wait_until_node_is_synced(&io).await?; + + let args = args.to_sdk(&mut ctx)?; + let namada = ctx.to_sdk(client, io); + let args = args.into_ibc_transfer(&namada).await?; + + tx::submit_ibc_transfer(&namada, args).await?; + } Sub::TxUpdateAccount(TxUpdateAccount(args)) => { let chain_ctx = ctx.borrow_mut_chain_or_exit(); let ledger_address = diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index c31d1901aa..9caa909e1c 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -24,6 +24,7 @@ use namada_apps_lib::governance::pgf::storage::steward::StewardDetail; use namada_apps_lib::governance::storage::proposal::ProposalType; use namada_apps_lib::governance::storage::vote::ProposalVote; use namada_apps_lib::governance::{InitProposalData, VoteProposalData}; +use namada_apps_lib::ibc::context::middlewares::create_transfer_middlewares; use namada_apps_lib::ibc::core::channel::types::channel::Order; use namada_apps_lib::ibc::core::channel::types::msgs::MsgChannelOpenInit; use namada_apps_lib::ibc::core::channel::types::Version as ChannelVersion; @@ -35,9 +36,7 @@ use namada_apps_lib::ibc::core::host::types::identifiers::{ ClientId, ConnectionId, PortId, }; use namada_apps_lib::ibc::primitives::ToProto; -use namada_apps_lib::ibc::{ - IbcActions, NftTransferModule, TransferModule, COMMITMENT_PREFIX, -}; +use namada_apps_lib::ibc::{IbcActions, NftTransferModule, COMMITMENT_PREFIX}; use namada_apps_lib::masp_primitives::merkle_tree::CommitmentTree; use namada_apps_lib::masp_primitives::transaction::Transaction; use namada_apps_lib::masp_proofs::sapling::SaplingVerificationContextInner; @@ -1725,7 +1724,10 @@ fn ibc_vp_validate_action(c: &mut Criterion) { ); actions.set_validation_params(ibc.validation_params().unwrap()); - let module = TransferModule::new(ctx.clone(), verifiers); + let module = create_transfer_middlewares::<_, parameters::Store<_>>( + ctx.clone(), + verifiers, + ); actions.add_transfer_module(module); let module = NftTransferModule::<_, token::Store<()>>::new(ctx); actions.add_transfer_module(module); @@ -1786,7 +1788,10 @@ fn ibc_vp_execute_action(c: &mut Criterion) { ); actions.set_validation_params(ibc.validation_params().unwrap()); - let module = TransferModule::new(ctx.clone(), verifiers); + let module = create_transfer_middlewares::<_, parameters::Store<_>>( + ctx.clone(), + verifiers, + ); actions.add_transfer_module(module); let module = NftTransferModule::<_, token::Store<()>>::new(ctx); actions.add_transfer_module(module); diff --git a/crates/core/src/address.rs b/crates/core/src/address.rs index 7023b70b4a..962b40d66c 100644 --- a/crates/core/src/address.rs +++ b/crates/core/src/address.rs @@ -393,11 +393,22 @@ impl Debug for Address { } } -// compute an Address from an IBC signer -impl TryFrom for Address { +impl From<&Address> for Signer { + fn from(address: &Address) -> Signer { + address.to_string().into() + } +} + +impl From
for Signer { + fn from(address: Address) -> Signer { + (&address).into() + } +} + +impl TryFrom<&Signer> for Address { type Error = DecodeError; - fn try_from(signer: Signer) -> Result { + fn try_from(signer: &Signer) -> Result { // The given address should be an address or payment address. When // sending a token from a spending key, it has been already // replaced with the MASP address. @@ -412,6 +423,14 @@ impl TryFrom for Address { } } +impl TryFrom for Address { + type Error = DecodeError; + + fn try_from(signer: Signer) -> Result { + (&signer).try_into() + } +} + /// An established address is generated on-chain #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive( diff --git a/crates/core/src/token.rs b/crates/core/src/token.rs index 5346922ed7..5c161f43be 100644 --- a/crates/core/src/token.rs +++ b/crates/core/src/token.rs @@ -528,6 +528,15 @@ impl DenominatedAmount { .ok_or(AmountParseError::PrecisionOverflow) } + /// Create a new [`DenominatedAmount`] with the same underlying + /// amout but a new denomination. + pub fn redenominate(self, new_denom: u8) -> Self { + Self { + amount: self.amount, + denom: new_denom.into(), + } + } + /// Multiply this number by 10^denom and return the computed integer if /// possible. Otherwise error out. pub fn scale( diff --git a/crates/ibc/Cargo.toml b/crates/ibc/Cargo.toml index 39f0c77965..c6f2be9004 100644 --- a/crates/ibc/Cargo.toml +++ b/crates/ibc/Cargo.toml @@ -40,10 +40,15 @@ namada_vp = { path = "../vp" } arbitrary = { workspace = true, optional = true } borsh.workspace = true data-encoding.workspace = true +dur.workspace = true konst.workspace = true linkme = {workspace = true, optional = true} ibc.workspace = true ibc-derive.workspace = true +ibc-middleware-module.workspace = true +ibc-middleware-module-macros.workspace = true +ibc-middleware-overflow-receive.workspace = true +ibc-middleware-packet-forward.workspace = true ibc-testkit = {workspace = true, optional = true} ics23.workspace = true masp_primitives.workspace = true diff --git a/crates/ibc/src/context/common.rs b/crates/ibc/src/context/common.rs index 7f920d8cb3..35233fd70e 100644 --- a/crates/ibc/src/context/common.rs +++ b/crates/ibc/src/context/common.rs @@ -576,14 +576,11 @@ pub trait IbcCommonContext: IbcStorageContext { port_id: &PortId, channel_id: &ChannelId, sequence: Sequence, - ) -> Result { + ) -> Result> { let key = storage::ack_key(port_id, channel_id, sequence); match self.storage().read_bytes(&key)? { - Some(value) => Ok(value.into()), - None => { - Err(PacketError::PacketAcknowledgementNotFound { sequence } - .into()) - } + Some(value) => Ok(Some(value.into())), + None => Ok(None), } } diff --git a/crates/ibc/src/context/middlewares.rs b/crates/ibc/src/context/middlewares.rs new file mode 100644 index 0000000000..d563071bde --- /dev/null +++ b/crates/ibc/src/context/middlewares.rs @@ -0,0 +1,65 @@ +//! Middleware entry points on Namada. + +pub mod pfm_mod; +pub mod shielded_recv; + +use std::cell::RefCell; +use std::collections::BTreeSet; +use std::fmt::Debug; +use std::marker::PhantomData; +use std::rc::Rc; + +use ibc::core::host::types::identifiers::PortId; +use ibc::core::router::module::Module; +use ibc::core::router::types::module::ModuleId; +use ibc_middleware_overflow_receive::OverflowReceiveMiddleware; +use ibc_middleware_packet_forward::PacketForwardMiddleware; +use namada_core::address::Address; + +use self::pfm_mod::PfmTransferModule; +use self::shielded_recv::ShieldedRecvModule; +use crate::context::transfer_mod::TransferModule; +use crate::{IbcCommonContext, IbcStorageContext}; + +/// The stack of middlewares of the transfer module. +pub type TransferMiddlewares = + OverflowReceiveMiddleware>; + +/// Create a new instance of [`TransferMiddlewares`] +pub fn create_transfer_middlewares( + ctx: Rc>, + verifiers: Rc>>, +) -> TransferMiddlewares +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + OverflowReceiveMiddleware::wrap(ShieldedRecvModule { + next: PacketForwardMiddleware::wrap(PfmTransferModule { + transfer_module: TransferModule::new(ctx, verifiers), + _phantom: PhantomData, + }), + }) +} + +impl crate::ModuleWrapper for TransferMiddlewares +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + fn as_module(&self) -> &dyn Module { + self + } + + fn as_module_mut(&mut self) -> &mut dyn Module { + self + } + + fn module_id(&self) -> ModuleId { + ModuleId::new(ibc::apps::transfer::types::MODULE_ID_STR.to_string()) + } + + fn port_id(&self) -> PortId { + PortId::transfer() + } +} diff --git a/crates/ibc/src/context/middlewares/pfm_mod.rs b/crates/ibc/src/context/middlewares/pfm_mod.rs new file mode 100644 index 0000000000..f414695a5f --- /dev/null +++ b/crates/ibc/src/context/middlewares/pfm_mod.rs @@ -0,0 +1,305 @@ +//! Implementation of Packet Forward Middleware on top of the ICS-20 +//! [`TransferModule`]. + +use std::fmt::{Debug, Formatter}; +use std::marker::PhantomData; + +use ibc::apps::transfer::context::TokenTransferExecutionContext; +use ibc::apps::transfer::handler::{ + refund_packet_token_execute, send_transfer_execute, +}; +use ibc::apps::transfer::types::msgs::transfer::MsgTransfer; +use ibc::apps::transfer::types::packet::PacketData; +use ibc::apps::transfer::types::{is_receiver_chain_source, TracePrefix}; +use ibc::core::channel::handler::{ + commit_packet_acknowledgment, emit_packet_acknowledgement_event, +}; +use ibc::core::channel::types::acknowledgement::Acknowledgement; +use ibc::core::channel::types::channel::{Counterparty, Order}; +use ibc::core::channel::types::error::{ChannelError, PacketError}; +use ibc::core::channel::types::packet::Packet; +use ibc::core::channel::types::timeout::TimeoutTimestamp; +use ibc::core::channel::types::Version; +use ibc::core::host::types::identifiers::{ + ChannelId, ConnectionId, PortId, Sequence, +}; +use ibc::core::router::module::Module; +use ibc::core::router::types::module::ModuleExtras; +use ibc::primitives::Signer; +use ibc_middleware_module::MiddlewareModule; +use ibc_middleware_module_macros::from_middleware; +use ibc_middleware_packet_forward::{ + InFlightPacket, InFlightPacketKey, PfmContext, +}; +use namada_core::address::{IBC as IBC_ADDRESS, MULTITOKEN}; +use namada_state::{StorageRead, StorageWrite}; + +use crate::context::transfer_mod::TransferModule; +use crate::context::IbcContext; +use crate::storage::inflight_packet_key; +use crate::{Error, IbcCommonContext, IbcStorageContext, TokenTransferContext}; + +/// A wrapper around an IBC transfer module necessary to +/// build execution contexts. This allows us to implement +/// packet forward middleware on this struct. +pub struct PfmTransferModule +where + C: IbcCommonContext + Debug, +{ + /// The main module + pub transfer_module: TransferModule, + #[allow(missing_docs)] + pub _phantom: PhantomData, +} + +impl Debug + for PfmTransferModule +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!(PfmTransferModule)) + .field("transfer_module", &self.transfer_module) + .finish_non_exhaustive() + } +} + +from_middleware! { + impl Module for PfmTransferModule + where + C: IbcCommonContext + Debug, +} + +impl MiddlewareModule for PfmTransferModule +where + C: IbcCommonContext + Debug, +{ + type NextMiddleware = TransferModule; + + fn next_middleware(&self) -> &Self::NextMiddleware { + &self.transfer_module + } + + fn next_middleware_mut(&mut self) -> &mut Self::NextMiddleware { + &mut self.transfer_module + } + + fn middleware_on_recv_packet_execute( + &mut self, + packet: &Packet, + relayer: &Signer, + ) -> (ModuleExtras, Option) { + let Ok(packet_data) = + serde_json::from_slice::(&packet.data) + else { + return self + .transfer_module + .on_recv_packet_execute(packet, relayer); + }; + + if crate::is_packet_forward(&packet_data) { + self.transfer_module.ctx.enable_parse_addr_as_governance(); + let ret = + self.transfer_module.on_recv_packet_execute(packet, relayer); + self.transfer_module.ctx.disable_parse_addr_as_governance(); + ret + } else { + self.transfer_module.on_recv_packet_execute(packet, relayer) + } + } +} + +impl PfmContext for PfmTransferModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + type Error = crate::Error; + + fn send_transfer_execute( + &mut self, + msg: MsgTransfer, + ) -> Result { + let seq = self + .transfer_module + .ctx + .inner + .borrow() + .get_next_sequence_send(&msg.port_id_on_a, &msg.chan_id_on_a) + .map_err(|e| Error::Context(Box::new(e)))?; + tracing::debug!(?seq, ?msg, "PFM send_transfer_execute"); + + let mut ctx = IbcContext::::new( + self.transfer_module.ctx.inner.clone(), + ); + let mut token_transfer_ctx = TokenTransferContext::new( + self.transfer_module.ctx.inner.clone(), + Default::default(), + ); + + self.transfer_module.ctx.insert_verifier(&MULTITOKEN); + + send_transfer_execute(&mut ctx, &mut token_transfer_ctx, msg) + .map_err(Error::TokenTransfer)?; + + Ok(seq) + } + + fn receive_refund_execute( + &mut self, + packet: &Packet, + data: PacketData, + ) -> Result<(), Self::Error> { + tracing::debug!(?packet, ?data, "PFM receive_refund_execute"); + let mut token_transfer_ctx = TokenTransferContext::new( + self.transfer_module.ctx.inner.clone(), + self.transfer_module.ctx.verifiers.clone(), + ); + self.transfer_module.ctx.insert_verifier(&MULTITOKEN); + refund_packet_token_execute(&mut token_transfer_ctx, packet, &data) + .map_err(Error::TokenTransfer) + } + + fn send_refund_execute( + &mut self, + msg: &InFlightPacket, + ) -> Result<(), Self::Error> { + tracing::debug!(?msg, "PFM send_refund_execute"); + + let packet_data: PacketData = serde_json::from_slice(&msg.packet_data) + .expect( + "The in-flight packet data should have belonged to an ICS-20 \ + packet", + ); + + let mut token_transfer_ctx = TokenTransferContext::new( + self.transfer_module.ctx.inner.clone(), + self.transfer_module.ctx.verifiers.clone(), + ); + + self.transfer_module.ctx.insert_verifier(&MULTITOKEN); + + if is_receiver_chain_source( + msg.packet_src_port_id.clone(), + msg.packet_src_channel_id.clone(), + &packet_data.token.denom, + ) { + let coin = { + let mut c = packet_data.token; + c.denom.remove_trace_prefix(&TracePrefix::new( + msg.packet_src_port_id.clone(), + msg.packet_src_channel_id.clone(), + )); + c + }; + + token_transfer_ctx + .escrow_coins_execute( + &IBC_ADDRESS, + &msg.refund_port_id, + &msg.refund_channel_id, + &coin, + &String::new().into(), + ) + .map_err(Error::TokenTransfer) + } else { + let coin = { + let mut c = packet_data.token; + c.denom.add_trace_prefix(TracePrefix::new( + msg.refund_port_id.clone(), + msg.refund_channel_id.clone(), + )); + c + }; + + token_transfer_ctx + .burn_coins_execute(&IBC_ADDRESS, &coin, &String::new().into()) + .map_err(Error::TokenTransfer) + } + } + + fn write_ack_and_events( + &mut self, + packet: &Packet, + acknowledgement: &Acknowledgement, + ) -> Result<(), Self::Error> { + tracing::debug!(?packet, ?acknowledgement, "PFM write_ack_and_events"); + let mut ctx = IbcContext::::new( + self.transfer_module.ctx.inner.clone(), + ); + commit_packet_acknowledgment(&mut ctx, packet, acknowledgement) + .map_err(|e| Error::Context(Box::new(e)))?; + emit_packet_acknowledgement_event( + &mut ctx, + packet.clone(), + acknowledgement.clone(), + ) + .map_err(|e| Error::Context(Box::new(e))) + } + + fn override_receiver( + &self, + _channel: &ChannelId, + _original_sender: &Signer, + ) -> Result { + Ok(IBC_ADDRESS.to_string().into()) + } + + #[allow(clippy::arithmetic_side_effects)] + fn timeout_timestamp( + &self, + timeout_duration: dur::Duration, + ) -> Result { + let timestamp = self + .transfer_module + .ctx + .inner + .borrow() + .host_timestamp() + .map_err(|e| Error::Other(e.to_string()))? + + timeout_duration.try_to_std().ok_or_else(|| { + Error::Other(format!( + "Packet timeout duration is too large: {timeout_duration}" + )) + })?; + let ts = timestamp + .map(TimeoutTimestamp::At) + .map_err(|e| Error::Other(e.to_string()))?; + tracing::debug!(timeout_timestamp = ?ts, "PFM timeout_timestamp"); + Ok(ts) + } + + fn store_inflight_packet( + &mut self, + key: InFlightPacketKey, + inflight_packet: InFlightPacket, + ) -> Result<(), Self::Error> { + tracing::debug!(?key, ?inflight_packet, "PFM store_inflight_packet"); + let mut ctx = self.transfer_module.ctx.inner.borrow_mut(); + let key = inflight_packet_key(&key); + ctx.storage_mut() + .write(&key, inflight_packet) + .map_err(Error::Storage) + } + + fn retrieve_inflight_packet( + &self, + key: &InFlightPacketKey, + ) -> Result, Self::Error> { + let mut ctx = self.transfer_module.ctx.inner.borrow_mut(); + let key = inflight_packet_key(key); + let packet = ctx.storage_mut().read(&key).map_err(Error::Storage); + + tracing::debug!(?key, ?packet, "PFM retrieve_inflight_packet"); + + packet + } + + fn delete_inflight_packet( + &mut self, + key: &InFlightPacketKey, + ) -> Result<(), Self::Error> { + tracing::debug!(?key, "PFM delete_inflight_packet"); + let mut ctx = self.transfer_module.ctx.inner.borrow_mut(); + let key = inflight_packet_key(key); + ctx.storage_mut().delete(&key).map_err(Error::Storage) + } +} diff --git a/crates/ibc/src/context/middlewares/shielded_recv.rs b/crates/ibc/src/context/middlewares/shielded_recv.rs new file mode 100644 index 0000000000..add826d6f9 --- /dev/null +++ b/crates/ibc/src/context/middlewares/shielded_recv.rs @@ -0,0 +1,214 @@ +//! This middleware is to handle automatically shielding the results of a +//! shielded swap. +//! +//! Since we do not know the resulting amount of assets from the swap ahead of +//! time, we cannot create a MASP note at the onset. We instead, create a note +//! for the minimum amount, which will be shielded. All assets exceeding the +//! minimum amount will be transferred to an overflow address specified by +//! the user. + +use std::cell::RefCell; +use std::collections::BTreeSet; +use std::fmt::{Debug, Formatter}; +use std::rc::Rc; + +use ibc::apps::transfer::context::TokenTransferExecutionContext; +use ibc::apps::transfer::types::packet::PacketData; +use ibc::apps::transfer::types::{Coin, PrefixedDenom}; +use ibc::core::channel::types::acknowledgement::{ + Acknowledgement, AcknowledgementStatus, StatusValue as AckStatusValue, +}; +use ibc::core::channel::types::channel::{Counterparty, Order}; +use ibc::core::channel::types::error::{ChannelError, PacketError}; +use ibc::core::channel::types::packet::Packet; +use ibc::core::channel::types::Version; +use ibc::core::host::types::identifiers::{ChannelId, ConnectionId, PortId}; +use ibc::core::router::module::Module; +use ibc::core::router::types::module::ModuleExtras; +use ibc::primitives::Signer; +use ibc_middleware_module::MiddlewareModule; +use ibc_middleware_module_macros::from_middleware; +use ibc_middleware_overflow_receive::OverflowRecvContext; +use ibc_middleware_packet_forward::PacketForwardMiddleware; +use namada_core::address::{Address, MASP}; +use namada_core::token; +use serde_json::{Map, Value}; + +use crate::context::middlewares::pfm_mod::PfmTransferModule; +use crate::msg::{NamadaMemo, OsmosisSwapMemoData}; +use crate::{Error, IbcCommonContext, IbcStorageContext, TokenTransferContext}; + +/// A middleware for handling IBC pockets received +/// after a shielded swap. The minimum amount will +/// be shielded and the rest placed in an overflow +/// account. +pub struct ShieldedRecvModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + /// The next middleware module + pub next: PacketForwardMiddleware>, +} + +impl ShieldedRecvModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + fn get_ctx(&self) -> Rc> { + self.next.next().transfer_module.ctx.inner.clone() + } + + fn get_verifiers(&self) -> Rc>> { + self.next.next().transfer_module.ctx.verifiers.clone() + } +} + +impl Debug for ShieldedRecvModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct(stringify!(ShieldedRecvModule)) + .field("next", &self.next) + .finish() + } +} + +from_middleware! { + impl Module for ShieldedRecvModule + where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +} + +impl MiddlewareModule for ShieldedRecvModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + type NextMiddleware = PacketForwardMiddleware>; + + fn next_middleware(&self) -> &Self::NextMiddleware { + &self.next + } + + fn next_middleware_mut(&mut self) -> &mut Self::NextMiddleware { + &mut self.next + } + + fn middleware_on_recv_packet_execute( + &mut self, + packet: &Packet, + relayer: &Signer, + ) -> (ModuleExtras, Option) { + let Ok(data) = serde_json::from_slice::(&packet.data) + else { + // NB: this isn't an ICS-20 packet + return self.next.on_recv_packet_execute(packet, relayer); + }; + if serde_json::from_str::>( + data.memo.as_ref(), + ) + .is_err() + { + // NB: this isn't a shielded recv packet + return self.next.on_recv_packet_execute(packet, relayer); + } + + if data.receiver.as_ref() != MASP.to_string() { + let ack = AcknowledgementStatus::error( + AckStatusValue::new(format!( + "Shielded receive error: Address {:?} is not the MASP", + data.receiver.as_ref() + )) + .expect("Ack is not empty"), + ); + return (ModuleExtras::empty(), Some(ack.into())); + } + + self.next.on_recv_packet_execute(packet, relayer) + } +} + +impl ibc_middleware_overflow_receive::PacketMetadata + for NamadaMemo +{ + type AccountId = Address; + type Amount = token::Amount; + + fn is_overflow_receive_msg(msg: &Map) -> bool { + msg.get("namada").map_or(false, |maybe_namada_obj| { + maybe_namada_obj + .as_object() + .map_or(false, |namada| namada.contains_key("osmosis_swap")) + }) + } + + fn strip_middleware_msg( + mut json_obj_memo: Map, + ) -> Map { + let namada_obj = json_obj_memo + .get_mut("namada") + .unwrap() + .as_object_mut() + .unwrap(); + + let mut osmosis_swap = namada_obj.remove("osmosis_swap").unwrap(); + let osmosis_swap_obj = osmosis_swap.as_object_mut().unwrap(); + + let shielding_data = osmosis_swap_obj.remove("shielding_data").unwrap(); + namada_obj.insert("memo".to_owned(), shielding_data); + + json_obj_memo + } + + fn overflow_receiver(&self) -> &Address { + &self.namada.osmosis_swap.overflow_receiver + } + + fn target_amount(&self) -> &token::Amount { + &self.namada.osmosis_swap.shielded_amount + } +} + +impl OverflowRecvContext for ShieldedRecvModule +where + C: IbcCommonContext + Debug, + Params: namada_systems::parameters::Read<::Storage>, +{ + type Error = Error; + type PacketMetadata = NamadaMemo; + + fn mint_coins_execute( + &mut self, + receiver: &Address, + coin: &Coin, + ) -> Result<(), Self::Error> { + let ctx = self.get_ctx(); + let verifiers = self.get_verifiers(); + let mut token_transfer_context = + TokenTransferContext::new(ctx, verifiers); + token_transfer_context + .mint_coins_execute(receiver, coin) + .map_err(Error::TokenTransfer) + } + + fn unescrow_coins_execute( + &mut self, + receiver: &Address, + port: &PortId, + channel: &ChannelId, + coin: &Coin, + ) -> Result<(), Self::Error> { + let ctx = self.get_ctx(); + let verifiers = self.get_verifiers(); + let mut token_transfer_context = + TokenTransferContext::new(ctx, verifiers); + token_transfer_context + .unescrow_coins_execute(receiver, port, channel, coin) + .map_err(Error::TokenTransfer) + } +} diff --git a/crates/ibc/src/context/mod.rs b/crates/ibc/src/context/mod.rs index d6e238c6c9..447ed02fc5 100644 --- a/crates/ibc/src/context/mod.rs +++ b/crates/ibc/src/context/mod.rs @@ -3,6 +3,7 @@ pub mod client; pub mod common; pub mod execution; +pub mod middlewares; pub mod nft_transfer; pub mod nft_transfer_mod; pub mod router; diff --git a/crates/ibc/src/context/nft_transfer.rs b/crates/ibc/src/context/nft_transfer.rs index d2c50e90b1..af5304d0e9 100644 --- a/crates/ibc/src/context/nft_transfer.rs +++ b/crates/ibc/src/context/nft_transfer.rs @@ -27,7 +27,7 @@ pub struct NftTransferContext where C: IbcCommonContext, { - inner: Rc>, + pub(crate) inner: Rc>, is_shielded: bool, _marker: PhantomData, } diff --git a/crates/ibc/src/context/nft_transfer_mod.rs b/crates/ibc/src/context/nft_transfer_mod.rs index 420d80362e..b8450468ce 100644 --- a/crates/ibc/src/context/nft_transfer_mod.rs +++ b/crates/ibc/src/context/nft_transfer_mod.rs @@ -261,7 +261,7 @@ where &mut self, packet: &Packet, _relayer: &Signer, - ) -> (ModuleExtras, Acknowledgement) { + ) -> (ModuleExtras, Option) { on_recv_packet_execute(&mut self.ctx, packet) } @@ -482,10 +482,10 @@ pub mod testing { &mut self, _packet: &Packet, _relayer: &Signer, - ) -> (ModuleExtras, Acknowledgement) { + ) -> (ModuleExtras, Option) { ( ModuleExtras::empty(), - AcknowledgementStatus::success(ack_success_b64()).into(), + Some(AcknowledgementStatus::success(ack_success_b64()).into()), ) } diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index da3bedc41c..789d7ca635 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -12,6 +12,7 @@ use ibc::apps::transfer::types::{Memo, PrefixedCoin, PrefixedDenom}; use ibc::core::channel::types::error::ChannelError; use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use ibc::core::primitives::Signer; use namada_core::address::{Address, InternalAddress, MASP}; use namada_core::token::Amount; use namada_core::uint::Uint; @@ -25,9 +26,10 @@ pub struct TokenTransferContext where C: IbcCommonContext, { - inner: Rc>, - verifiers: Rc>>, + pub(crate) inner: Rc>, + pub(crate) verifiers: Rc>>, is_shielded: bool, + parse_addr_as_governance: bool, } impl TokenTransferContext @@ -43,14 +45,25 @@ where inner, verifiers, is_shielded: false, + parse_addr_as_governance: false, } } /// Insert a verifier address whose VP will verify the tx. - fn insert_verifier(&mut self, addr: &Address) { + pub(crate) fn insert_verifier(&mut self, addr: &Address) { self.verifiers.borrow_mut().insert(addr.clone()); } + /// Enable parsing ibc signers as the governance address + pub fn enable_parse_addr_as_governance(&mut self) { + self.parse_addr_as_governance = true; + } + + /// Disable parsing ibc signers as the governance address + pub fn disable_parse_addr_as_governance(&mut self) { + self.parse_addr_as_governance = false; + } + /// Set to enable a shielded transfer pub fn enable_shielded_transfer(&mut self) { self.is_shielded = true; @@ -179,6 +192,24 @@ where { type AccountId = Address; + fn sender_account_from_signer( + &self, + signer: &Signer, + ) -> Option { + Address::decode(signer.as_ref()).ok() + } + + fn receiver_account_from_signer( + &self, + signer: &Signer, + ) -> Option { + if self.parse_addr_as_governance { + Some(namada_core::address::GOV) + } else { + Address::try_from(signer).ok() + } + } + fn get_port(&self) -> Result { Ok(PortId::transfer()) } diff --git a/crates/ibc/src/context/transfer_mod.rs b/crates/ibc/src/context/transfer_mod.rs index 9a1075e280..a30826514d 100644 --- a/crates/ibc/src/context/transfer_mod.rs +++ b/crates/ibc/src/context/transfer_mod.rs @@ -276,7 +276,7 @@ where &mut self, packet: &Packet, _relayer: &Signer, - ) -> (ModuleExtras, Acknowledgement) { + ) -> (ModuleExtras, Option) { on_recv_packet_execute(&mut self.ctx, packet) } @@ -497,10 +497,10 @@ pub mod testing { &mut self, _packet: &Packet, _relayer: &Signer, - ) -> (ModuleExtras, Acknowledgement) { + ) -> (ModuleExtras, Option) { ( ModuleExtras::empty(), - AcknowledgementStatus::success(ack_success_b64()).into(), + Some(AcknowledgementStatus::success(ack_success_b64()).into()), ) } diff --git a/crates/ibc/src/context/validation.rs b/crates/ibc/src/context/validation.rs index 0f00feefcf..7173a14a82 100644 --- a/crates/ibc/src/context/validation.rs +++ b/crates/ibc/src/context/validation.rs @@ -5,6 +5,7 @@ use ibc::core::channel::types::channel::ChannelEnd; use ibc::core::channel::types::commitment::{ AcknowledgementCommitment, PacketCommitment, }; +use ibc::core::channel::types::error::PacketError; use ibc::core::channel::types::packet::Receipt; use ibc::core::client::context::{ ClientValidationContext, ExtClientValidationContext, @@ -253,11 +254,18 @@ where &self, path: &AckPath, ) -> Result { - self.inner.borrow().packet_ack( + let maybe_ack = self.inner.borrow().packet_ack( &path.port_id, &path.channel_id, path.sequence, - ) + )?; + + maybe_ack.ok_or_else(|| { + PacketError::PacketAcknowledgementNotFound { + sequence: path.sequence, + } + .into() + }) } fn channel_counter(&self) -> Result { diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index 9fa49d58aa..69a064dcb6 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -80,6 +80,7 @@ use ibc::core::host::types::identifiers::{ChannelId, PortId, Sequence}; use ibc::core::router::types::error::RouterError; use ibc::primitives::proto::Any; pub use ibc::*; +use ibc_middleware_packet_forward::PacketMetadata; use masp_primitives::transaction::Transaction as MaspTransaction; pub use msg::*; use namada_core::address::{self, Address}; @@ -143,6 +144,8 @@ pub enum Error { ChainId(IdentifierError), #[error("Verifier insertion error: {0}")] Verifier(StorageError), + #[error("Storage read/write error: {0}")] + Storage(StorageError), #[error("IBC error: {0}")] Other(String), } @@ -690,7 +693,9 @@ where // Extract MASP tx from the memo in the packet if needed let masp_tx = match &*envelope { MsgEnvelope::Packet(PacketMsg::Recv(msg)) - if self.is_receiving_success(msg)? => + if self + .is_receiving_success(msg)? + .is_some_and(|ack_succ| ack_succ) => { extract_masp_tx_from_packet(&msg.packet) } @@ -713,8 +718,8 @@ where pub fn is_receiving_success( &self, msg: &IbcMsgRecvPacket, - ) -> Result { - let packet_ack = self + ) -> Result, Error> { + let Some(packet_ack) = self .ctx .inner .borrow() @@ -723,11 +728,14 @@ where &msg.packet.chan_id_on_b, msg.packet.seq_on_a, ) - .map_err(|e| Error::Context(Box::new(e)))?; + .map_err(|e| Error::Context(Box::new(e)))? + else { + return Ok(None); + }; let success_ack_commitment = compute_ack_commitment( &AcknowledgementStatus::success(ack_success_b64()).into(), ); - Ok(packet_ack == success_ack_commitment) + Ok(Some(packet_ack == success_ack_commitment)) } /// Validate according to the message in IBC VP @@ -786,6 +794,10 @@ where } } +fn is_packet_forward(data: &PacketData) -> bool { + serde_json::from_str::(data.memo.as_ref()).is_ok() +} + // Extract the involved namada address from the packet (either sender or // receiver) to trigger its vp. Returns None if an address could not be found fn get_envelope_verifier( @@ -795,9 +807,14 @@ fn get_envelope_verifier( MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { match msg.packet.port_id_on_b.as_str() { FT_PORT_ID_STR => { - serde_json::from_slice::(&msg.packet.data) - .ok() - .map(|packet_data| packet_data.receiver) + let packet_data = + serde_json::from_slice::(&msg.packet.data) + .ok()?; + if is_packet_forward(&packet_data) { + None + } else { + Some(packet_data.receiver) + } } NFT_PORT_ID_STR => { serde_json::from_slice::(&msg.packet.data) diff --git a/crates/ibc/src/msg.rs b/crates/ibc/src/msg.rs index 8a15817c00..c5d75407df 100644 --- a/crates/ibc/src/msg.rs +++ b/crates/ibc/src/msg.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::fmt; +use std::str::FromStr; use borsh::schema::{Declaration, Definition, Fields}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -16,6 +18,106 @@ use ibc::core::host::types::identifiers::PortId; use ibc::primitives::proto::Protobuf; use masp_primitives::transaction::Transaction as MaspTransaction; use namada_core::borsh::BorshSerializeExt; +use namada_core::string_encoding::StringEncoded; +use serde::{Deserialize, Serialize}; + +trait Sealed {} + +/// Marker trait that denotes whether an IBC memo is valid +/// in Namada. +#[allow(private_bounds)] +pub trait ValidNamadaMemo: Sealed {} + +impl Sealed for NamadaMemo {} +impl ValidNamadaMemo for NamadaMemo {} + +impl Sealed for NamadaMemo {} +impl ValidNamadaMemo for NamadaMemo {} + +/// Osmosis swap memo data. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct OsmosisSwapMemoData { + /// The inner memo data. + pub osmosis_swap: OsmosisSwapMemoDataInner, +} + +/// Osmosis swap inner memo data. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct OsmosisSwapMemoDataInner { + /// Shielding transfer data. Hex encodes the borsh serialized MASP + /// transfer. + pub shielding_data: StringEncoded, + /// The amount that is shielded onto the MASP. Corresponds to the + /// minimum output amount from the swap. + pub shielded_amount: namada_core::token::Amount, + /// The receiver of the difference between the transferred tokens and + /// the minimum output amount. + pub overflow_receiver: namada_core::address::Address, +} + +impl From> for NamadaMemo { + fn from(memo: NamadaMemo) -> Self { + memo.namada.into() + } +} + +impl From for NamadaMemo { + fn from( + OsmosisSwapMemoData { + osmosis_swap: + OsmosisSwapMemoDataInner { + shielding_data, + shielded_amount, + overflow_receiver, + }, + }: OsmosisSwapMemoData, + ) -> Self { + Self { + namada: NamadaMemoData::OsmosisSwap { + overflow_receiver, + shielded_amount, + shielding_data, + }, + } + } +} + +impl From for NamadaMemo { + fn from(data: OsmosisSwapMemoData) -> Self { + Self { namada: data } + } +} + +/// Memo data serialized as a JSON object included +/// in IBC packets. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct NamadaMemo { + /// The inner memo data. + pub namada: Data, +} + +/// Data included in a Namada memo. +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum NamadaMemoData { + /// Generic message sent over IBC. + Memo(String), + /// Osmosis swap message. + OsmosisSwap { + /// Shielding transfer data. Hex encodes the borsh serialized MASP + /// transfer. + shielding_data: StringEncoded, + /// The amount that is shielded onto the MASP. Corresponds to the + /// minimum output amount from the swap. + shielded_amount: namada_core::token::Amount, + /// The receiver of the difference between the transferred tokens and + /// the minimum output amount. + overflow_receiver: namada_core::address::Address, + }, +} /// The different variants of an Ibc message #[derive(Debug, Clone)] @@ -132,9 +234,32 @@ impl BorshSchema for MsgNftTransfer { #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct IbcShieldingData(pub MaspTransaction); +impl From<&IbcShieldingData> for String { + fn from(data: &IbcShieldingData) -> Self { + HEXUPPER.encode(&data.serialize_to_vec()) + } +} + impl From for String { fn from(data: IbcShieldingData) -> Self { - HEXUPPER.encode(&data.serialize_to_vec()) + (&data).into() + } +} + +impl fmt::Display for IbcShieldingData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", String::from(self)) + } +} + +impl FromStr for IbcShieldingData { + type Err = String; + + fn from_str(s: &str) -> Result { + let bytes = HEXUPPER + .decode(s.as_bytes()) + .map_err(|err| err.to_string())?; + IbcShieldingData::try_from_slice(&bytes).map_err(|err| err.to_string()) } } @@ -154,8 +279,17 @@ pub fn extract_masp_tx_from_envelope( pub fn decode_ibc_shielding_data( s: impl AsRef, ) -> Option { - let bytes = HEXUPPER.decode(s.as_ref().as_bytes()).ok()?; - IbcShieldingData::try_from_slice(&bytes).ok() + let sref = s.as_ref(); + + serde_json::from_str(sref).map_or_else( + |_| sref.parse().ok(), + |NamadaMemo { namada: memo_data }| match memo_data { + NamadaMemoData::Memo(memo) => memo.parse().ok(), + NamadaMemoData::OsmosisSwap { shielding_data, .. } => { + Some(shielding_data.raw) + } + }, + ) } /// Extract MASP transaction from IBC packet memo diff --git a/crates/ibc/src/storage.rs b/crates/ibc/src/storage.rs index 38b0d7e905..37754320ce 100644 --- a/crates/ibc/src/storage.rs +++ b/crates/ibc/src/storage.rs @@ -13,6 +13,7 @@ use ibc::core::host::types::path::{ ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, UpgradeClientStatePath, UpgradeConsensusStatePath, }; +use ibc_middleware_packet_forward::InFlightPacketKey; use namada_core::address::{Address, InternalAddress}; use namada_core::storage::{DbKeySeg, Key, KeySeg}; use namada_core::token::Amount; @@ -647,3 +648,23 @@ pub fn withdraw_key(token: &Address) -> Key { .push(&token.to_string().to_db_key()) .expect("Cannot obtain a storage key") } + +/// Get a middleware key prefix. +pub fn middlewares_prefix() -> Key { + const MIDDLEWARES_SUBKEY: &str = "middleware"; + + let key: Key = namada_core::address::IBC.to_db_key().into(); + key.with_segment(MIDDLEWARES_SUBKEY.to_string()) +} + +/// Get the Namada storage key associated with the provided +/// [`InFlightPacketKey`]. +pub fn inflight_packet_key(inflight_packet_key: &InFlightPacketKey) -> Key { + const PFM_SUBKEY: &str = "pfm"; + + middlewares_prefix() + .with_segment(PFM_SUBKEY.to_string()) + .with_segment(inflight_packet_key.port.to_string()) + .with_segment(inflight_packet_key.channel.to_string()) + .with_segment(inflight_packet_key.sequence.to_string()) +} diff --git a/crates/ibc/src/vp/mod.rs b/crates/ibc/src/vp/mod.rs index ebff0d9196..410fd75f83 100644 --- a/crates/ibc/src/vp/mod.rs +++ b/crates/ibc/src/vp/mod.rs @@ -27,6 +27,7 @@ use namada_vp::native_vp::{Ctx, CtxPreStorageRead, NativeVp, VpEvaluator}; use namada_vp::VpEnv; use thiserror::Error; +use crate::context::middlewares::create_transfer_middlewares; use crate::core::host::types::identifiers::ChainId as IbcChainId; use crate::core::host::types::path::UPGRADED_IBC_STATE; use crate::event::IbcEvent; @@ -36,8 +37,8 @@ use crate::storage::{ }; use crate::trace::calc_hash; use crate::{ - Error as ActionError, IbcActions, NftTransferModule, TransferModule, - ValidationParams, COMMITMENT_PREFIX, + Error as ActionError, IbcActions, NftTransferModule, ValidationParams, + COMMITMENT_PREFIX, }; #[allow(missing_docs)] @@ -246,7 +247,10 @@ where ctx.clone(), verifiers.clone(), ); - let module = TransferModule::new(ctx.clone(), verifiers); + let module = create_transfer_middlewares::<_, ParamsPseudo>( + ctx.clone(), + verifiers, + ); actions.add_transfer_module(module); let module = NftTransferModule::<_, Token>::new(ctx.clone()); actions.add_transfer_module(module); @@ -301,7 +305,8 @@ where IbcActions::<_, Params, Token>::new(ctx.clone(), verifiers.clone()); actions.set_validation_params(self.validation_params()?); - let module = TransferModule::new(ctx.clone(), verifiers); + let module = + create_transfer_middlewares::<_, Params>(ctx.clone(), verifiers); actions.add_transfer_module(module); let module = NftTransferModule::<_, Token>::new(ctx); actions.add_transfer_module(module); diff --git a/crates/sdk/Cargo.toml b/crates/sdk/Cargo.toml index 517a0baaa1..f81e052373 100644 --- a/crates/sdk/Cargo.toml +++ b/crates/sdk/Cargo.toml @@ -91,6 +91,7 @@ namada_wallet = {path = "../wallet" } arbitrary = { workspace = true, optional = true } async-trait.workspace = true +bech32.workspace = true bimap.workspace = true borsh.workspace = true circular-queue.workspace = true diff --git a/crates/sdk/src/args.rs b/crates/sdk/src/args.rs index d7a3558ca8..824f14b045 100644 --- a/crates/sdk/src/args.rs +++ b/crates/sdk/src/args.rs @@ -8,7 +8,7 @@ use std::time::Duration as StdDuration; use either::Either; use masp_primitives::transaction::components::sapling::builder::BuildParams; use masp_primitives::zip32::PseudoExtendedKey; -use namada_core::address::Address; +use namada_core::address::{Address, MASP}; use namada_core::chain::{BlockHeight, ChainId, Epoch}; use namada_core::collections::HashMap; use namada_core::dec::Dec; @@ -16,7 +16,9 @@ use namada_core::ethereum_events::EthAddress; use namada_core::keccak::KeccakHash; use namada_core::key::{common, SchemeType}; use namada_core::masp::{MaspEpoch, PaymentAddress}; +use namada_core::string_encoding::StringEncoded; use namada_core::time::DateTimeUtc; +use namada_core::token::Amount; use namada_core::{storage, token}; use namada_governance::cli::onchain::{ DefaultProposal, PgfFundingProposal, PgfStewardProposal, @@ -28,8 +30,11 @@ use namada_tx::Memo; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; +use crate::error::Error; use crate::eth_bridge::bridge_pool; use crate::ibc::core::host::types::identifiers::{ChannelId, PortId}; +use crate::ibc::{NamadaMemo, NamadaMemoData}; +use crate::rpc::query_osmosis_pool_routes; use crate::signing::SigningTxData; use crate::wallet::{DatedSpendingKey, DatedViewingKey}; use crate::{rpc, tx, Namada}; @@ -465,6 +470,293 @@ impl TxUnshieldingTransfer { } } +/// Individual hop of some route to take through Osmosis pools. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OsmosisPoolHop { + /// The id of the pool to use on Osmosis. + pub pool_id: String, + /// The output denomination expected from the + /// pool on Osmosis. + pub token_out_denom: String, +} + +impl FromStr for OsmosisPoolHop { + type Err = String; + + fn from_str(s: &str) -> Result { + s.split_once(':').map_or_else( + || { + Err(format!( + "Expected : string, but found \ + {s:?} instead" + )) + }, + |(pool_id, token_out_denom)| { + Ok(OsmosisPoolHop { + pool_id: pool_id.to_owned(), + token_out_denom: token_out_denom.to_owned(), + }) + }, + ) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +/// Constraints on the osmosis swap +pub enum Slippage { + /// Specifies the minimum amount to be received + MinOutputAmount(Amount), + /// A time-weighted average price + Twap { + /// The maximum percentage difference allowed between the estimated and + /// actual trade price. This must be a decimal number in the range + /// `[0, 100]`. + slippage_percentage: String, + /// The time period (in seconds) over which the average price is + /// calculated + window_seconds: u64, + }, +} + +/// An token swap on Osmosis +#[derive(Debug, Clone)] +pub struct TxOsmosisSwap { + /// The IBC transfer data + pub transfer: TxIbcTransfer, + /// The token we wish to receive (on Osmosis) + pub output_denom: String, + /// Address of the recipient on Namada + pub recipient: Either, + /// Address to receive funds exceeding the minimum amount + pub overflow: Option, + /// Constraints on the osmosis swap + pub slippage: Slippage, + /// Recovery address (on Osmosis) in case of failure + pub local_recovery_addr: String, + /// The route to take through Osmosis pools + pub route: Option>, +} + +impl TxOsmosisSwap { + /// Create an IBC transfer from the input arguments + pub async fn into_ibc_transfer( + self, + ctx: &impl Namada, + ) -> crate::error::Result> { + #[derive(Serialize)] + struct Memo { + wasm: Wasm, + } + + #[derive(Serialize)] + struct Wasm { + contract: String, + msg: Message, + } + + #[derive(Serialize)] + struct Message { + osmosis_swap: OsmosisSwap, + } + + #[derive(Serialize)] + struct OsmosisSwap { + receiver: String, + output_denom: String, + slippage: Slippage, + on_failed_delivery: LocalRecoveryAddr, + route: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + next_memo: Option>, + } + + #[derive(Serialize)] + struct LocalRecoveryAddr { + local_recovery_addr: String, + } + + #[inline] + fn assert_json_obj( + value: serde_json::Value, + ) -> serde_json::Map { + match value { + serde_json::Value::Object(x) => x, + _ => unreachable!(), + } + } + + const OSMOSIS_SQS_SERVER: &str = "https://sqsprod.osmosis.zone"; + + let Self { + mut transfer, + output_denom, + recipient, + slippage, + local_recovery_addr, + route, + overflow, + } = self; + + let recipient = recipient + .map_either(Some, |payment_addr| Some(payment_addr).zip(overflow)) + .factor_none() + .ok_or_else(|| { + Error::Other( + "Overflow receiver unspecified while attempting a fully \ + shielded swap" + .to_owned(), + ) + })?; + + // validate `local_recovery_addr` and the contract addr + if !bech32::decode(&local_recovery_addr) + .is_ok_and(|(hrp, _, _)| hrp == "osmo") + { + // TODO: validate that addr has 20 bytes? + return Err(Error::Other(format!( + "Invalid Osmosis recovery address {local_recovery_addr:?}" + ))); + } + if !bech32::decode(&transfer.receiver) + .is_ok_and(|(hrp, _, _)| hrp == "osmo") + { + // TODO: validate that addr has 32 bytes? + return Err(Error::Other(format!( + "Invalid Osmosis contract address {local_recovery_addr:?}" + ))); + } + + let route = if let Some(route) = route { + route + } else { + query_osmosis_pool_routes( + ctx, + &transfer.token, + transfer.amount, + transfer.channel_id.clone(), + &output_denom, + OSMOSIS_SQS_SERVER, + ) + .await? + .pop() + .ok_or_else(|| { + Error::Other(format!( + "No route found to swap {:?} of Namada's {} with Osmosis' \ + {}", + transfer.amount, transfer.token, output_denom + )) + })? + }; + + let (receiver, slippage, next_memo) = match recipient { + Either::Left(transparent_recipient) => { + (transparent_recipient.to_string(), slippage, None) + } + Either::Right((payment_addr, overflow_receiver)) => { + let amount_to_shield = match slippage { + Slippage::MinOutputAmount(amount_to_shield) => { + amount_to_shield + } + Slippage::Twap { .. } => todo!( + "Cannot compute min output amount from slippage TWAP \ + yet" + ), + }; + + let shielding_tx = tx::gen_ibc_shielding_transfer( + ctx, + GenIbcShieldingTransfer { + query: Query { + ledger_address: transfer.tx.ledger_address.clone(), + }, + output_folder: None, + target: + namada_core::masp::TransferTarget::PaymentAddress( + payment_addr, + ), + token: output_denom.clone(), + amount: InputAmount::Validated( + token::DenominatedAmount::new( + amount_to_shield, + 0u8.into(), + ), + ), + expiration: transfer.tx.expiration.clone(), + port_id: transfer.port_id.clone(), + channel_id: transfer.channel_id.clone(), + }, + ) + .await? + .ok_or_else(|| { + Error::Other( + "Failed to generate IBC shielding transfer".to_owned(), + ) + })?; + + let memo = assert_json_obj( + serde_json::to_value(&NamadaMemo { + namada: NamadaMemoData::OsmosisSwap { + shielding_data: StringEncoded::new( + IbcShieldingData(shielding_tx), + ), + shielded_amount: amount_to_shield, + overflow_receiver, + }, + }) + .unwrap(), + ); + + ( + MASP.to_string(), + Slippage::MinOutputAmount(amount_to_shield), + Some(memo), + ) + } + }; + + let cosmwasm_memo = Memo { + wasm: Wasm { + contract: transfer.receiver.clone(), + msg: Message { + osmosis_swap: OsmosisSwap { + output_denom, + slippage, + next_memo, + receiver, + on_failed_delivery: LocalRecoveryAddr { + local_recovery_addr, + }, + route, + }, + }, + }, + }; + let namada_memo = transfer.ibc_memo.take().map(|memo| { + assert_json_obj( + serde_json::to_value(&NamadaMemo { + namada: NamadaMemoData::Memo(memo), + }) + .unwrap(), + ) + }); + + let memo = { + let mut m = serde_json::to_value(&cosmwasm_memo).unwrap(); + let m_obj = m.as_object_mut().unwrap(); + + if let Some(mut namada_memo) = namada_memo { + m_obj.append(&mut namada_memo); + } + + m + }; + + transfer.ibc_memo = Some(serde_json::to_string(&memo).unwrap()); + Ok(transfer) + } +} + /// IBC transfer transaction arguments #[derive(Clone, Debug)] pub struct TxIbcTransfer { diff --git a/crates/sdk/src/rpc.rs b/crates/sdk/src/rpc.rs index 9f747aa815..bbb30e755a 100644 --- a/crates/sdk/src/rpc.rs +++ b/crates/sdk/src/rpc.rs @@ -2,6 +2,7 @@ #![allow(clippy::result_large_err)] +use core::str::FromStr; use std::cell::Cell; use std::collections::{BTreeMap, BTreeSet}; use std::ops::ControlFlow; @@ -16,6 +17,9 @@ use namada_core::arith::checked; use namada_core::chain::{BlockHeight, Epoch}; use namada_core::collections::{HashMap, HashSet}; use namada_core::hash::Hash; +use namada_core::ibc::apps::nft_transfer::types::TracePrefix; +use namada_core::ibc::apps::transfer::types::PrefixedDenom; +use namada_core::ibc::core::host::types::identifiers::ChannelId; use namada_core::ibc::IbcTokenHash; use namada_core::key::common; use namada_core::masp::MaspEpoch; @@ -34,9 +38,11 @@ use namada_governance::storage::proposal::StorageProposal; use namada_governance::utils::{ compute_proposal_result, ProposalResult, ProposalVotes, Vote, }; +use namada_ibc::core::host::types::identifiers::PortId; use namada_ibc::storage::{ ibc_trace_key, ibc_trace_key_prefix, is_ibc_trace_key, }; +use namada_ibc::trace::calc_ibc_token_hash; use namada_io::{display_line, edisplay_line, Client, Io}; use namada_parameters::{storage as params_storage, EpochDuration}; use namada_proof_of_stake::parameters::PosParams; @@ -49,9 +55,9 @@ use namada_state::LastBlock; use namada_token::masp::MaspTokenRewardData; use namada_tx::data::{BatchedTxResult, DryRunResult, ResultCode, TxResult}; use namada_tx::event::{Batch as BatchAttr, Code as CodeAttr}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -use crate::args::InputAmount; +use crate::args::{InputAmount, OsmosisPoolHop}; use crate::control_flow::time; use crate::error::{EncodingError, Error, QueryError, TxSubmitError}; use crate::events::{extend, Event}; @@ -63,6 +69,7 @@ use crate::queries::RPC; use crate::tendermint::block::Height; use crate::tendermint::merkle::proof::ProofOps; use crate::tendermint_rpc::query::Query; +use crate::tx::get_ibc_src_port_channel; use crate::{error, Namada, Tx}; /// Query an estimate of the maximum block time. @@ -1488,3 +1495,132 @@ pub async fn query_ibc_denom( token.as_ref().to_string() } + +/// Query a route of Osmosis liquidity pools +/// for swapping betwixt token and output_denom +/// assets. +pub async fn query_osmosis_pool_routes( + ctx: &impl Namada, + token: &Address, + amount: InputAmount, + channel_id: ChannelId, + output_denom: &str, + osmosis_sqs_server_url: &str, +) -> Result>, Error> { + #[derive(Deserialize)] + struct PoolHop { + id: u64, + token_out_denom: String, + } + + impl From for OsmosisPoolHop { + fn from(value: PoolHop) -> Self { + Self { + pool_id: value.id.to_string(), + token_out_denom: value.token_out_denom, + } + } + } + + #[derive(Deserialize)] + struct Route { + pools: Vec, + } + + #[derive(Deserialize)] + struct ResponseOk { + route: Vec, + } + + #[derive(Deserialize)] + struct ResponseErr { + message: String, + } + + let coin = { + let denom = query_ibc_denom(ctx, token.to_string(), None).await; + let amount = validate_amount(ctx, amount, token, false).await?; + + let PrefixedDenom { + mut trace_path, + base_denom, + } = PrefixedDenom::from_str(&denom).map_err(|_| { + Error::Other(format!( + "Could not decode {token} as an IBC token address" + )) + })?; + + let prefix_on_namada = + TracePrefix::new(PortId::transfer(), channel_id.clone()); + + if trace_path.starts_with(&prefix_on_namada) { + // we received an asset from osmosis, so the asset we + // send back won't have our `transfer/channel` prefix + trace_path.remove_prefix(&prefix_on_namada); + } else { + // in this case, osmosis will prefix the asset it receives + // with the channel to namada + let channel = + get_ibc_src_port_channel(ctx, &PortId::transfer(), &channel_id) + .await? + .1; + trace_path + .add_prefix(TracePrefix::new(PortId::transfer(), channel)); + } + + let amount = amount.redenominate(0); + + let token_denom = if trace_path.is_empty() { + base_denom.to_string() + } else { + format!( + "ibc/{}", + calc_ibc_token_hash( + PrefixedDenom { + trace_path, + base_denom + } + .to_string() + ) + ) + }; + + format!("{amount}{token_denom}") + }; + + let client = reqwest::Client::new(); + let response = client + .get(format!("{osmosis_sqs_server_url}/router/quote")) + .query(&[ + ("tokenIn", coin.as_str()), + ("tokenOutDenom", output_denom), + ("humanDenoms", "false"), + ]) + .send() + .await + .map_err(|err| { + Error::Other(format!("Failed to query Osmosis SQS: {err}",)) + })?; + + if !response.status().is_success() { + let ResponseErr { message } = response.json().await.map_err(|err| { + Error::Other(format!( + "Failed to read failure response from HTTP request body: {err}" + )) + })?; + return Err(Error::Other(format!( + "Invalid Osmosis SQS query: {message}" + ))); + } + + let ResponseOk { route } = response.json().await.map_err(|err| { + Error::Other(format!( + "Failed to read success response from HTTP request body: {err}" + )) + })?; + + Ok(route + .into_iter() + .map(|r| r.pools.into_iter().map(OsmosisPoolHop::from).collect()) + .collect()) +} diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 19a6a5ba17..a274ee435b 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -3936,7 +3936,7 @@ pub async fn gen_ibc_shielding_transfer( Ok(shielded_transfer.map(|st| st.masp_tx)) } -async fn get_ibc_src_port_channel( +pub(crate) async fn get_ibc_src_port_channel( context: &impl Namada, dest_port_id: &PortId, dest_channel_id: &ChannelId, diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index add26e1621..9ce5aca59e 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -38,7 +38,9 @@ namada_vm = {path = "../vm", features = ["testing"]} concat-idents.workspace = true derivative.workspace = true +dur.workspace = true hyper = {version = "0.14.20", features = ["full"]} +ibc-middleware-packet-forward.workspace = true ibc-testkit.workspace = true ics23.workspace = true itertools.workspace = true diff --git a/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_registry.wasm b/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_registry.wasm new file mode 100644 index 0000000000..cec0d6a833 Binary files /dev/null and b/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_registry.wasm differ diff --git a/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_swaps.wasm b/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_swaps.wasm new file mode 100644 index 0000000000..8546925c05 Binary files /dev/null and b/crates/tests/fixtures/osmosis_data/wasm_bytecode/crosschain_swaps.wasm differ diff --git a/crates/tests/fixtures/osmosis_data/wasm_bytecode/swaprouter.wasm b/crates/tests/fixtures/osmosis_data/wasm_bytecode/swaprouter.wasm new file mode 100644 index 0000000000..c3dfe5d156 Binary files /dev/null and b/crates/tests/fixtures/osmosis_data/wasm_bytecode/swaprouter.wasm differ diff --git a/crates/tests/src/e2e/helpers.rs b/crates/tests/src/e2e/helpers.rs index 5c3a9d25a1..c5ee6c2721 100644 --- a/crates/tests/src/e2e/helpers.rs +++ b/crates/tests/src/e2e/helpers.rs @@ -31,8 +31,8 @@ use namada_sdk::wallet::Wallet; use toml::Value; use super::setup::{ - self, run_cosmos_cmd, sleep, NamadaBgCmd, NamadaCmd, Test, ENV_VAR_DEBUG, - ENV_VAR_USE_PREBUILT_BINARIES, + self, run_cosmos_cmd, sleep, NamadaBgCmd, NamadaCmd, Test, TestDir, + ENV_VAR_DEBUG, ENV_VAR_USE_PREBUILT_BINARIES, }; use crate::e2e::setup::{constants, Bin, CosmosChainType, Who, APPS_PACKAGE}; use crate::strings::{LEDGER_STARTED, TX_APPLIED_SUCCESS}; @@ -493,7 +493,12 @@ pub fn epochs_per_year_from_min_duration(min_duration: u64) -> u64 { } /// Make a Hermes config -pub fn make_hermes_config(test_a: &Test, test_b: &Test) -> Result<()> { +pub fn make_hermes_config( + hermes_dir: &TestDir, + test_a: &Test, + test_b: &Test, + relayer: Option<&str>, +) -> Result<()> { let mut config = toml::map::Map::new(); let mut global = toml::map::Map::new(); @@ -531,28 +536,24 @@ pub fn make_hermes_config(test_a: &Test, test_b: &Test) -> Result<()> { config.insert("telemetry".to_owned(), Value::Table(telemetry)); let chains = vec![ - make_hermes_chain_config(test_a), + match CosmosChainType::chain_type(test_a.net.chain_id.as_str()) { + Ok(chain_type) => make_hermes_chain_config_for_cosmos( + hermes_dir, chain_type, test_a, relayer, + ), + Err(_) => make_hermes_chain_config(hermes_dir, test_a), + }, match CosmosChainType::chain_type(test_b.net.chain_id.as_str()) { - Ok(chain_type) => { - make_hermes_chain_config_for_cosmos(chain_type, test_b) - } - Err(_) => make_hermes_chain_config(test_b), + Ok(chain_type) => make_hermes_chain_config_for_cosmos( + hermes_dir, chain_type, test_b, relayer, + ), + Err(_) => make_hermes_chain_config(hermes_dir, test_b), }, ]; config.insert("chains".to_owned(), Value::Array(chains)); let toml_string = toml::to_string(&Value::Table(config)).unwrap(); - let hermes_dir = test_a.test_dir.as_ref().join("hermes"); - std::fs::create_dir_all(&hermes_dir).unwrap(); - let config_path = hermes_dir.join("config.toml"); - let mut file = File::create(config_path).unwrap(); - file.write_all(toml_string.as_bytes()).map_err(|e| { - eyre!(format!("Writing a Hermes config failed: {}", e,)) - })?; - // One Hermes config.toml is OK, but add one more config.toml to execute - // Hermes from test_b - let hermes_dir = test_b.test_dir.as_ref().join("hermes"); + let hermes_dir = hermes_dir.as_ref().join("hermes"); std::fs::create_dir_all(&hermes_dir).unwrap(); let config_path = hermes_dir.join("config.toml"); let mut file = File::create(config_path).unwrap(); @@ -563,7 +564,7 @@ pub fn make_hermes_config(test_a: &Test, test_b: &Test) -> Result<()> { Ok(()) } -fn make_hermes_chain_config(test: &Test) -> Value { +fn make_hermes_chain_config(hermes_dir: &TestDir, test: &Test) -> Value { let chain_id = test.net.chain_id.as_str(); let rpc_addr = get_actor_rpc(test, Who::Validator(0)); let rpc_addr = rpc_addr.strip_prefix("http://").unwrap(); @@ -603,16 +604,28 @@ fn make_hermes_chain_config(test: &Test) -> Value { chain.insert("max_block_time".to_owned(), Value::String("60s".to_owned())); + let hermes_dir: &Path = hermes_dir.as_ref(); + let key_dir = hermes_dir.join("hermes/keys"); + chain.insert( + "key_store_folder".to_owned(), + Value::String(key_dir.to_string_lossy().to_string()), + ); + Value::Table(chain) } fn make_hermes_chain_config_for_cosmos( + hermes_dir: &TestDir, chain_type: CosmosChainType, test: &Test, + relayer: Option<&str>, ) -> Value { let mut table = toml::map::Map::new(); table.insert("mode".to_owned(), Value::String("push".to_owned())); - let url = format!("ws://{}/websocket", setup::constants::COSMOS_RPC); + let url = format!( + "ws://127.0.0.1:{}/websocket", + chain_type.get_rpc_port_number() + ); table.insert("url".to_owned(), Value::String(url)); table.insert("batch_delay".to_owned(), Value::String("500ms".to_owned())); let event_source = Value::Table(table); @@ -623,14 +636,22 @@ fn make_hermes_chain_config_for_cosmos( Value::String(test.net.chain_id.to_string()), ); chain.insert("type".to_owned(), Value::String("CosmosSdk".to_owned())); + chain.insert( "rpc_addr".to_owned(), - Value::String(format!("http://{}", setup::constants::COSMOS_RPC)), + Value::String(format!( + "http://127.0.0.1:{}", + chain_type.get_rpc_port_number() + )), ); chain.insert( "grpc_addr".to_owned(), - Value::String("http://127.0.0.1:9090".to_owned()), + Value::String(format!( + "http://127.0.0.1:{}", + chain_type.get_grpc_port_number() + )), ); + chain.insert("event_source".to_owned(), event_source); chain.insert( "account_prefix".to_owned(), @@ -638,18 +659,23 @@ fn make_hermes_chain_config_for_cosmos( ); chain.insert( "key_name".to_owned(), - Value::String(setup::constants::COSMOS_RELAYER.to_string()), + Value::String(relayer.unwrap_or_else(|| constants::COSMOS_RELAYER).to_string()), ); - let key_dir = test.test_dir.as_ref().join("hermes/keys"); + let hermes_dir: &Path = hermes_dir.as_ref(); + let key_dir = hermes_dir.join("hermes/keys"); chain.insert( "key_store_folder".to_owned(), Value::String(key_dir.to_string_lossy().to_string()), ); chain.insert("store_prefix".to_owned(), Value::String("ibc".to_owned())); chain.insert("max_gas".to_owned(), Value::Integer(500_000)); - chain.insert("gas_multiplier".to_owned(), Value::Float(1.3)); + chain.insert("gas_multiplier".to_owned(), Value::Float(2.3)); let mut table = toml::map::Map::new(); - table.insert("price".to_owned(), Value::Float(0.001)); + if let CosmosChainType::Osmosis = chain_type { + table.insert("price".to_owned(), Value::Float(0.01)); + } else { + table.insert("price".to_owned(), Value::Float(0.001)); + } table.insert("denom".to_owned(), Value::String("stake".to_string())); chain.insert("gas_price".to_owned(), Value::Table(table)); @@ -672,6 +698,29 @@ pub fn update_cosmos_config(test: &Test) -> Result<()> { *timeout_propose = "1s".into(); } } + let chain_type = + CosmosChainType::chain_type(test.net.chain_id.as_str()).unwrap(); + let p2p = values + .get_mut("p2p") + .expect("Test failed") + .as_table_mut() + .expect("Test failed"); + let Some(laddr) = p2p.get_mut("laddr") else { + panic!("Test failed") + }; + *laddr = + format!("tcp://0.0.0.0:{}", chain_type.get_p2p_port_number()).into(); + let rpc = values + .get_mut("rpc") + .expect("Test failed") + .as_table_mut() + .expect("Test failed"); + let Some(laddr) = rpc.get_mut("laddr") else { + panic!("Test failed") + }; + *laddr = + format!("tcp://0.0.0.0:{}", chain_type.get_rpc_port_number()).into(); + let mut file = OpenOptions::new() .write(true) .truncate(true) @@ -735,9 +784,36 @@ pub fn update_cosmos_config(test: &Test) -> Result<()> { serde_json::to_writer_pretty(writer, &genesis) .expect("Writing Cosmos genesis.toml failed"); + if matches!(chain_type, CosmosChainType::Osmosis) { + let client_path = cosmos_dir.join("config/client.toml"); + let s = std::fs::read_to_string(&client_path) + .expect("Reading Osmosis client config failed"); + let mut values = s + .parse::() + .expect("Parsing Osmosis client config failed"); + let Some(laddr) = values.get_mut("node") else { + panic!("Test failed") + }; + *laddr = + format!("tcp://0.0.0.0:{}", chain_type.get_rpc_port_number()).into(); + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(&client_path)?; + file.write_all(values.to_string().as_bytes()).map_err(|e| { + eyre!(format!("Writing a Osmosis client config file failed: {}", e)) + })?; + } + Ok(()) } +pub fn get_cosmos_rpc_address(test: &Test) -> String { + let chain_type = + CosmosChainType::chain_type(test.net.chain_id.as_str()).unwrap(); + format!("127.0.0.1:{}", chain_type.get_rpc_port_number()) +} + pub fn find_cosmos_address( test: &Test, alias: impl AsRef, @@ -759,7 +835,8 @@ pub fn find_cosmos_address( } pub fn get_cosmos_gov_address(test: &Test) -> Result { - let args = ["query", "auth", "module-account", "gov"]; + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); + let args = ["query", "auth", "module-account", "gov", "--node", &rpc]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; let (_, matched) = cosmos.exp_regex("cosmos[a-z0-9]+")?; diff --git a/crates/tests/src/e2e/ibc_tests.rs b/crates/tests/src/e2e/ibc_tests.rs index c4c09694cc..3b02f6f825 100644 --- a/crates/tests/src/e2e/ibc_tests.rs +++ b/crates/tests/src/e2e/ibc_tests.rs @@ -15,6 +15,8 @@ use std::path::{Path, PathBuf}; use color_eyre::eyre::Result; use eyre::eyre; +use ibc_middleware_packet_forward::ForwardMetadata; +use itertools::Either; use namada_apps_lib::client::rpc::query_storage_value_bytes; use namada_apps_lib::config::ethereum_bridge; use namada_apps_lib::config::genesis::templates; @@ -42,22 +44,26 @@ use namada_sdk::ibc::trace::ibc_token; use namada_sdk::token::Amount; use namada_test_utils::TestWasms; use prost::Message; +use serde_json::json; use setup::constants::*; use sha2::{Digest, Sha256}; use crate::e2e::helpers::{ epoch_sleep, epochs_per_year_from_min_duration, find_address, find_cosmos_address, find_payment_address, get_actor_rpc, - get_cosmos_gov_address, get_epoch, + get_cosmos_gov_address, get_cosmos_rpc_address, get_epoch, }; use crate::e2e::ledger_tests::{ start_namada_ledger_node_wait_wasm, write_json_file, }; use crate::e2e::setup::{ - self, apply_use_device, run_cosmos_cmd, run_hermes_cmd, - set_ethereum_bridge_mode, setup_cosmos, setup_hermes, sleep, working_dir, - Bin, CosmosChainType, NamadaCmd, Test, Who, ENV_VAR_COSMWASM_CONTRACT_DIR, + self, apply_use_device, osmosis_fixtures_dir, run_cosmos_cmd, + run_cosmos_cmd_homeless, run_hermes_cmd, set_ethereum_bridge_mode, + setup_cosmos, setup_hermes, sleep, working_dir, Bin, CosmosChainType, + NamadaCmd, Test, TestDir, Who, ENV_VAR_COSMWASM_CONTRACT_DIR, }; +use crate::ibc::primitives::Signer; +use crate::ibc::IbcShieldingData; use crate::strings::TX_APPLIED_SUCCESS; use crate::{run, run_as}; @@ -101,14 +107,15 @@ fn ibc_transfers() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_namada = FT_PORT_ID.parse().unwrap(); let port_id_gaia = FT_PORT_ID.parse().unwrap(); let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, @@ -116,7 +123,7 @@ fn ibc_transfers() -> Result<()> { )?; // Start relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let bg_hermes = hermes.background(); // 1. Transparent transfers @@ -135,9 +142,15 @@ fn ibc_transfers() -> Result<()> { None, None, None, + None, false, )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; check_balance(&test, ALBERT, APFEL, 999_998)?; let token_addr = find_address(&test, APFEL)?; @@ -163,7 +176,7 @@ fn ibc_transfers() -> Result<()> { None, None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; // Check the balances check_balance(&test, ALBERT, APFEL, 999_999)?; @@ -186,7 +199,7 @@ fn ibc_transfers() -> Result<()> { None, None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; // Check the token on Namada let ibc_denom_on_namada = @@ -207,9 +220,15 @@ fn ibc_transfers() -> Result<()> { None, None, None, + None, false, )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // Check the balances check_balance(&test, ALBERT, &ibc_denom_on_namada, 100)?; @@ -244,10 +263,15 @@ fn ibc_transfers() -> Result<()> { 100, &port_id_gaia, &channel_id_gaia, - Some(shielding_data_path), + Some(Either::Left(shielding_data_path)), None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test_gaia)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; // Check the token on Namada check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 100)?; check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 800)?; @@ -279,9 +303,15 @@ fn ibc_transfers() -> Result<()> { None, None, None, + None, true, )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; check_shielded_balance(&test, AB_VIEWING_KEY, &ibc_denom_on_namada, 40)?; check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 810)?; @@ -305,10 +335,10 @@ fn ibc_transfers() -> Result<()> { 1_000_000, &port_id_gaia, &channel_id_gaia, - Some(memo_path), + Some(Either::Left(memo_path)), None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; // Check the token on Namada check_shielded_balance(&test, AA_VIEWING_KEY, APFEL, 1)?; @@ -328,9 +358,15 @@ fn ibc_transfers() -> Result<()> { None, None, None, + None, false, )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // The balance should not be changed check_balance(&test, ALBERT, APFEL, 999_999)?; @@ -351,16 +387,22 @@ fn ibc_transfers() -> Result<()> { Some(Duration::new(10, 0)), None, None, + None, false, )?; // wait for the timeout sleep(10); // Restart relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let bg_hermes = hermes.background(); - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // The balance should not be changed check_balance(&test, ALBERT, &ibc_denom_on_namada, 100)?; @@ -378,9 +420,15 @@ fn ibc_transfers() -> Result<()> { None, None, None, + None, true, )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // Check the token has been refunded to the refund target check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 40)?; check_balance(&test, IBC_REFUND_TARGET_ALIAS, &ibc_denom_on_namada, 10)?; @@ -403,16 +451,22 @@ fn ibc_transfers() -> Result<()> { Some(Duration::new(10, 0)), None, None, + None, true, )?; // wait for the timeout sleep(10); // Restart relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let _bg_hermes = hermes.background(); - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // Check the token has been refunded to the refund target check_shielded_balance(&test, AA_VIEWING_KEY, APFEL, 0)?; check_balance(&test, IBC_REFUND_TARGET_ALIAS, APFEL, 1)?; @@ -438,7 +492,12 @@ fn ibc_transfers() -> Result<()> { // MASP VP shall reject it, make it timeout Some(Duration::new(10, 0)), )?; - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // Check the balance didn't change check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 40)?; check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 810)?; @@ -462,11 +521,16 @@ fn ibc_transfers() -> Result<()> { 101, &port_id_gaia, &channel_id_gaia, - Some(shielding_data_path), + Some(Either::Left(shielding_data_path)), // MASP VP shall reject it, make it timeout Some(Duration::new(10, 0)), )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test_gaia)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_gaia, + &channel_id_gaia, + &test_gaia, + )?; // Check the balances didn't change check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_denom_on_namada, 40)?; check_cosmos_balance(&test_gaia, COSMOS_USER, COSMOS_COIN, 810)?; @@ -493,7 +557,7 @@ fn ibc_nft_transfers() -> Result<()> { let _bg_ledger = ledger.background(); let _bg_wasmd = cosmwasm.background(); - setup_hermes(&test, &test_cosmwasm)?; + let hermes_dir = setup_hermes(&test, &test_cosmwasm)?; let port_id_namada: PortId = NFT_PORT_ID.parse().unwrap(); let (cw721_contract, ics721_contract) = @@ -504,6 +568,7 @@ fn ibc_nft_transfers() -> Result<()> { let port_id_cosmwasm = get_cosmwasm_port_id(&test_cosmwasm, &ics721_contract)?; let (channel_id_namada, channel_id_cosmwasm) = create_channel_with_hermes( + &hermes_dir, &test, &test_cosmwasm, &port_id_namada, @@ -528,7 +593,12 @@ fn ibc_nft_transfers() -> Result<()> { None, None, )?; - clear_packet(&port_id_cosmwasm, &channel_id_cosmwasm, &test_cosmwasm)?; + clear_packet( + &hermes_dir, + &port_id_cosmwasm, + &channel_id_cosmwasm, + &test_cosmwasm, + )?; check_balance(&test, &namada_receiver, &ibc_trace_on_namada, 1)?; // Namada to CosmWasm @@ -544,9 +614,10 @@ fn ibc_nft_transfers() -> Result<()> { None, None, None, + None, false, )?; - clear_packet(&port_id_namada, &channel_id_namada, &test)?; + clear_packet(&hermes_dir, &port_id_namada, &channel_id_namada, &test)?; check_balance(&test, &namada_receiver, &ibc_trace_on_namada, 0)?; // 2. Shielding/Unshielding transfers @@ -572,7 +643,12 @@ fn ibc_nft_transfers() -> Result<()> { Some(shielding_data_path), None, )?; - clear_packet(&port_id_cosmwasm, &channel_id_cosmwasm, &test_cosmwasm)?; + clear_packet( + &hermes_dir, + &port_id_cosmwasm, + &channel_id_cosmwasm, + &test_cosmwasm, + )?; check_shielded_balance(&test, AA_VIEWING_KEY, &ibc_trace_on_namada, 1)?; // Shielded transfer on Namada @@ -602,9 +678,10 @@ fn ibc_nft_transfers() -> Result<()> { None, None, None, + None, true, )?; - clear_packet(&port_id_namada, &channel_id_namada, &test)?; + clear_packet(&hermes_dir, &port_id_namada, &channel_id_namada, &test)?; check_shielded_balance(&test, AB_VIEWING_KEY, &ibc_trace_on_namada, 0)?; Ok(()) @@ -627,14 +704,15 @@ fn pgf_over_ibc() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_namada = FT_PORT_ID.parse().unwrap(); let port_id_gaia: PortId = FT_PORT_ID.parse().unwrap(); let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, @@ -642,7 +720,7 @@ fn pgf_over_ibc() -> Result<()> { )?; // Start relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let _bg_hermes = hermes.background(); // Transfer to PGF account @@ -688,7 +766,12 @@ fn pgf_over_ibc() -> Result<()> { while epoch < grace_epoch { epoch = epoch_sleep(&test, &rpc, 120)?; } - wait_for_packet_relay(&port_id_namada, &channel_id_namada, &test)?; + wait_for_packet_relay( + &hermes_dir, + &port_id_namada, + &channel_id_namada, + &test, + )?; // Check balances after funding over IBC let token_addr = find_address(&test, NAM)?; @@ -724,7 +807,7 @@ fn fee_payment_with_ibc_token() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); @@ -747,10 +830,11 @@ fn fee_payment_with_ibc_token() -> Result<()> { submit_votes(&test)?; // Create an IBC channel while waiting the grace epoch - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_gaia = FT_PORT_ID.parse().unwrap(); let port_id_namada = FT_PORT_ID.parse().unwrap(); let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, @@ -759,7 +843,7 @@ fn fee_payment_with_ibc_token() -> Result<()> { let ibc_token_on_namada = format!("{port_id_namada}/{channel_id_namada}/{COSMOS_COIN}"); // Start relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let _bg_hermes = hermes.background(); // wait for the grace @@ -781,7 +865,7 @@ fn fee_payment_with_ibc_token() -> Result<()> { None, None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; // Check the token on Namada check_balance(&test, ALBERT_KEY, &ibc_token_on_namada, 250)?; @@ -830,7 +914,7 @@ fn ibc_token_inflation() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); @@ -853,17 +937,18 @@ fn ibc_token_inflation() -> Result<()> { submit_votes(&test)?; // Create an IBC channel while waiting the grace epoch - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_namada = FT_PORT_ID.parse().unwrap(); let port_id_gaia = FT_PORT_ID.parse().unwrap(); let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, &port_id_gaia, )?; // Start relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let _bg_hermes = hermes.background(); // wait for the grace @@ -893,10 +978,10 @@ fn ibc_token_inflation() -> Result<()> { 1, &port_id_gaia, &channel_id_gaia, - Some(shielding_data_path), + Some(Either::Left(shielding_data_path)), None, )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; // wait the next masp epoch to dispense the reward let mut epoch = get_epoch(&test, &rpc).unwrap(); @@ -922,14 +1007,16 @@ fn ibc_upgrade_client() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); + sleep(5); - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_namada = FT_PORT_ID.parse().unwrap(); let port_id_gaia: PortId = FT_PORT_ID.parse().unwrap(); create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, @@ -954,7 +1041,7 @@ fn ibc_upgrade_client() -> Result<()> { } // Upgrade the IBC client of Gaia on Namada with Hermes - upgrade_client(&test, test.net.chain_id.to_string(), upgrade_height)?; + upgrade_client(&hermes_dir, test.net.chain_id.to_string(), upgrade_height)?; // Check the upgraded client let upgraded_client_state = @@ -993,14 +1080,15 @@ fn ibc_rate_limit() -> Result<()> { setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) }; let (ledger, gaia, test, test_gaia) = - run_namada_cosmos(CosmosChainType::Gaia, update_genesis)?; + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; let _bg_ledger = ledger.background(); let _bg_gaia = gaia.background(); - setup_hermes(&test, &test_gaia)?; + let hermes_dir = setup_hermes(&test, &test_gaia)?; let port_id_namada = FT_PORT_ID.parse().unwrap(); let port_id_gaia: PortId = FT_PORT_ID.parse().unwrap(); let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, &test, &test_gaia, &port_id_namada, @@ -1008,7 +1096,7 @@ fn ibc_rate_limit() -> Result<()> { )?; // Start relaying - let hermes = run_hermes(&test)?; + let hermes = run_hermes(&hermes_dir)?; let _bg_hermes = hermes.background(); // wait for the next epoch @@ -1033,82 +1121,1002 @@ fn ibc_rate_limit() -> Result<()> { None, None, None, + None, + false, + )?; + + // Transfer 1 NAM from Namada to Gaia again will fail + transfer( + &test, + ALBERT, + &gaia_receiver, + NAM, + 1, + Some(ALBERT_KEY), + &port_id_namada, + &channel_id_namada, + None, + None, + // expect an error of the throughput limit + Some( + "Transfer exceeding the per-epoch throughput limit is not allowed", + ), + None, + false, + )?; + + // wait for the next epoch + let mut epoch = get_epoch(&test, &rpc).unwrap(); + let next_epoch = epoch.next(); + while epoch <= next_epoch { + epoch = epoch_sleep(&test, &rpc, 120)?; + } + + // Transfer 1 NAM from Namada to Gaia will succeed in the new epoch + transfer( + &test, + ALBERT, + &gaia_receiver, + NAM, + 1, + Some(ALBERT_KEY), + &port_id_namada, + &channel_id_namada, + None, + None, + None, + None, + false, + )?; + + // wait for the next epoch + let mut epoch = get_epoch(&test, &rpc).unwrap(); + let next_epoch = epoch.next(); + while epoch <= next_epoch { + sleep(5); + epoch = get_epoch(&test, &rpc).unwrap(); + } + + // Transfer 2 samoleans from Gaia to Namada will succeed, but Namada can't + // receive due to the mint limit and the packet will be timed out + let namada_receiver = find_address(&test, ALBERT)?.to_string(); + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + namada_receiver, + COSMOS_COIN, + 2, + &port_id_gaia, + &channel_id_gaia, + None, + Some(Duration::new(10, 0)), + )?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; + + // Check if Namada hasn't receive it + let ibc_denom = + format!("{port_id_namada}/{channel_id_namada}/{COSMOS_COIN}"); + // Need the raw address to check the balance because the token shouldn't be + // received + let token_addr = ibc_token(ibc_denom).to_string(); + check_balance(&test, ALBERT, token_addr, 0)?; + + Ok(()) +} + +/// Test the happy flows of ibc pfm +/// +/// Setup: Two instances of Gaia and one +/// Namada instance. +/// +/// 1. Test sending a transfer from first cosmos chain to the second via the PFM +/// module on Namada (henceforth denoted "via PFM") +/// 2. Test sending the above transfer back to first cosmos chain via PFM +/// 3. Send wrapped NAM from first cosmos chain to the second via PFM +/// 4. Reverse the transaction in the step above +#[test] +fn ibc_pfm_happy_flows() -> Result<()> { + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1800); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia_1, test, test_gaia_1) = + run_namada_cosmos(CosmosChainType::Gaia(Some(1)), update_genesis)?; + + // set up a second cosmos network + let test_gaia_2 = setup_cosmos(CosmosChainType::Gaia(Some(2)))?; + let gaia_2 = run_cosmos(&test_gaia_2, false)?; + sleep(5); + + let _bg_ledger = ledger.background(); + let _bg_gaia_1 = gaia_1.background(); + let _bg_gaia_2 = gaia_2.background(); + let hermes_gaia1_namada = setup_hermes(&test, &test_gaia_1)?; + let hermes_namada_gaia2 = setup_hermes(&test, &test_gaia_2)?; + sleep(5); + // create hermes relayers for connections between both cosmos chains and + // namada + let port_id_namada = FT_PORT_ID.parse().unwrap(); + let port_id_gaia_1 = FT_PORT_ID.parse().unwrap(); + let port_id_gaia_2 = FT_PORT_ID.parse().unwrap(); + + let (channel_id_gaia_1, channel_id_namada_1) = create_channel_with_hermes( + &hermes_gaia1_namada, + &test_gaia_1, + &test, + &port_id_namada, + &port_id_gaia_1, + )?; + let (channel_id_gaia_2, channel_id_namada_2) = create_channel_with_hermes( + &hermes_namada_gaia2, + &test_gaia_2, + &test, + &port_id_namada, + &port_id_gaia_2, + )?; + + // Start relaying + let hermes_1 = run_hermes(&hermes_gaia1_namada)?; + let hermes_2 = run_hermes(&hermes_namada_gaia2)?; + let _bg_hermes_1 = hermes_1.background(); + let _bg_hermes_2 = hermes_2.background(); + + // ------------------- Step 1. ------------------- + + // Bertha is our most trusted intermediary in all things + let namada_receiver = find_address(&test, BERTHA)?.to_string(); + // this will cause the ibc packet to be forwarded to the second + // cosmos chain. + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_2, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_2, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + "samoleans", + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + // Check the samoleans have been received on the second cosmos chain + check_cosmos_balance( + &test_gaia_2, + COSMOS_USER, + format!( + "transfer/{channel_id_gaia_2}/transfer/{channel_id_namada_1}/\ + samoleans" + ), + 10, + )?; + + // Check the samoleans have been sent from the first cosmos chain + check_cosmos_balance(&test_gaia_1, COSMOS_USER, "samoleans", 1000 - 10)?; + + // ------------------- Step 2. ------------------- + + // this will cause the ibc packet to be forwarded to the first + // cosmos chain. + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_1, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_1, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_2, + COSMOS_USER, + &namada_receiver, + format!( + "transfer/{channel_id_gaia_2}/transfer/{channel_id_namada_1}/\ + samoleans" + ), + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_namada, + &channel_id_namada_2, + &test, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_gaia_1, + &channel_id_gaia_1, + &test_gaia_1, + )?; + + // Check the samoleans have been received on the fist cosmos chain + check_cosmos_balance(&test_gaia_1, COSMOS_USER, "samoleans", 1000)?; + + // ------------------- Step 3. ------------------- + let nam_addr = find_address(&test, NAM)?; + // we first send some NAM to the first cosmos chain. + let gaia_receiver = find_cosmos_address(&test_gaia_1, COSMOS_USER)?; + transfer( + &test, + BERTHA, + gaia_receiver, + NAM, + 20, + Some(BERTHA_KEY), + &port_id_namada, + &channel_id_namada_1, + None, + None, + None, + None, false, )?; - // Transfer 1 NAM from Namada to Gaia again will fail - transfer( + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_gaia_1, + &channel_id_gaia_1, + &test_gaia_1, + )?; + + // Check the NAM have been sent to the first cosmos chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000, + )?; + + // this will cause the ibc packet to be forwarded to the second + // cosmos chain. + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_2, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_2, + None, + None, + ); + + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 10_000_000, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + // Check the NAM have been received on the second cosmos chain + check_cosmos_balance( + &test_gaia_2, + COSMOS_USER, + format!("transfer/{channel_id_gaia_2}/{nam_addr}"), + 10_000_000, + )?; + + // Check the NAM have been sent from the first cosmos chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000 - 10_000_000, + )?; + + // ------------------- Step 4. ------------------- + + // this will cause the ibc packet to be forwarded to the first + // cosmos chain. + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_1, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_1, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_2, + COSMOS_USER, + &namada_receiver, + format!("transfer/{channel_id_gaia_2}/{nam_addr}"), + 10_000_000, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_namada, + &channel_id_namada_2, + &test, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_gaia_1, + &channel_id_gaia_1, + &test_gaia_1, + )?; + + // Check the NAM have been received on the first cosmos chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000, + )?; + + Ok(()) +} + +/// Test the flows of ibc pfm where the packet cannot be +/// completed and refunds must be issued. +/// +/// Setup: Two instances of Gaia and one +/// Namada instance. +/// +/// 1. Test sending a transfer from first cosmos chain to the second via the PFM +/// module on Namada (henceforth denoted "via PFM"), failing on the second +/// cosmos chain due to an error. +/// 2. Same as above except that the failure occurs due to a time-out on the +/// second cosmos chain. +/// 3. Same as the first except that wrapped NAM is sent from the first cosmos +/// chain +/// 4. Same as above except that the failure occurs due to a time-out on the +/// second cosmos chain. +/// 5. Test sending assets from the second cosmos network to the first and then +/// failing to send it back to the second due to an error +/// 6. Same as above except that the failure occurs due to a time-out on the +/// second cosmos chain. +#[test] +fn ibc_pfm_unhappy_flows() -> Result<()> { + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1800); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia_1, test, test_gaia_1) = + run_namada_cosmos(CosmosChainType::Gaia(Some(1)), update_genesis)?; + + // set up a second cosmos network + let test_gaia_2 = setup_cosmos(CosmosChainType::Gaia(Some(2)))?; + let gaia_2 = run_cosmos(&test_gaia_2, false)?; + sleep(5); + + let _bg_ledger = ledger.background(); + let _bg_gaia_1 = gaia_1.background(); + let _bg_gaia_2 = gaia_2.background(); + let hermes_gaia1_namada = setup_hermes(&test, &test_gaia_1)?; + let hermes_namada_gaia2 = setup_hermes(&test, &test_gaia_2)?; + sleep(5); + // create hermes relayers for connections between both cosmos chains and + // namada + let port_id_namada = FT_PORT_ID.parse().unwrap(); + let port_id_gaia_1 = FT_PORT_ID.parse().unwrap(); + let port_id_gaia_2 = FT_PORT_ID.parse().unwrap(); + + let (channel_id_gaia_1, channel_id_namada_1) = create_channel_with_hermes( + &hermes_gaia1_namada, + &test_gaia_1, + &test, + &port_id_namada, + &port_id_gaia_1, + )?; + let (channel_id_gaia_2, channel_id_namada_2) = create_channel_with_hermes( + &hermes_namada_gaia2, + &test_gaia_2, + &test, + &port_id_namada, + &port_id_gaia_2, + )?; + + // Start relaying + let hermes_1 = run_hermes(&hermes_gaia1_namada)?; + let hermes_2 = run_hermes(&hermes_namada_gaia2)?; + let _bg_hermes_1 = hermes_1.background(); + let bg_hermes_2 = hermes_2.background(); + + // ------------------- Step 1. ------------------- + + // Bertha is our most trusted intermediary in all things + let namada_receiver = find_address(&test, BERTHA)?.to_string(); + // this will cause the ibc packet to be forwarded to the second + // cosmos chain. + let pfm_memo = packet_forward_memo( + // NB: since the receiver is invalid, the ICS-20 + // transfer will fail, and the first cosmos chain + // will receive an ack error + "Hodor".to_string().into(), + &port_id_namada, + &channel_id_namada_2, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + "samoleans", + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the samoleans have been refunded on the first chain + check_cosmos_balance(&test_gaia_1, COSMOS_USER, "samoleans", 1000)?; + + // ------------------- Step 2. ------------------- + + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_1, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_2, + Some(Duration::new(1, 0)), + None, + ); + // Stop Hermes for timeout test + let mut hermes_2 = bg_hermes_2.foreground(); + hermes_2.interrupt()?; + + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + "samoleans", + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + + // wait for the timeout + sleep(10); + + // Restart relaying + let hermes_2 = run_hermes(&hermes_namada_gaia2)?; + let bg_hermes_2 = hermes_2.background(); + + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the samoleans have been refunded on the first chain + check_cosmos_balance(&test_gaia_1, COSMOS_USER, "samoleans", 1000)?; + + // ------------------- Step 3. ------------------- + + let nam_addr = find_address(&test, NAM)?; + // we first send some NAM to the first cosmos chain. + let gaia_receiver = find_cosmos_address(&test_gaia_1, COSMOS_USER)?; + transfer( + &test, + BERTHA, + gaia_receiver, + NAM, + 20, + Some(BERTHA_KEY), + &port_id_namada, + &channel_id_namada_1, + None, + None, + None, + None, + false, + )?; + + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_gaia_1, + &channel_id_gaia_1, + &test_gaia_1, + )?; + + // Check the NAM have been sent to the first cosmos chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000, + )?; + + // this will cause the ibc packet to be forwarded to the second + // cosmos chain. + let pfm_memo = packet_forward_memo( + "Hodor".to_string().into(), + &port_id_namada, + &channel_id_namada_2, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 10_000_000, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the NAM have been refunded on the first chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000, + )?; + + // ------------------- Step 4. ------------------- + + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_2, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_2, + Some(Duration::new(1, 0)), + None, + ); + // Stop Hermes for timeout test + let mut hermes_2 = bg_hermes_2.foreground(); + hermes_2.interrupt()?; + + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 10_000_000, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + + // wait for the timeout + sleep(10); + + // Restart relaying + let hermes_2 = run_hermes(&hermes_namada_gaia2)?; + let bg_hermes_2 = hermes_2.background(); + + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the NAM have been refunded on the first chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!("transfer/{channel_id_gaia_1}/{nam_addr}"), + 20_000_000, + )?; + + // ------------------- Step 5. ------------------- + + // we first send some samoleans to the first cosmos chain from the second. + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_1, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_1, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_2, + COSMOS_USER, + &namada_receiver, + "samoleans", + 10, + &port_id_gaia_2, + &channel_id_gaia_2, + Some(Either::Right(pfm_memo)), + None, + )?; + + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_gaia_1, + &channel_id_gaia_1, + &test_gaia_1, + )?; + + // Check the non-native samoleans have been sent to the first cosmos chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!( + "transfer/{channel_id_gaia_1}/transfer/{channel_id_namada_2}/\ + samoleans" + ), + 10, + )?; + + // this will cause the ibc packet to be forwarded to the second + // cosmos chain. + let pfm_memo = packet_forward_memo( + "Hodor".to_string().into(), + &port_id_namada, + &channel_id_namada_2, + None, + None, + ); + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + format!( + "transfer/{channel_id_gaia_1}/transfer/{channel_id_namada_2}/\ + samoleans" + ), + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the non-native samoleans have been refunded on the first chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!( + "transfer/{channel_id_gaia_1}/transfer/{channel_id_namada_2}/\ + samoleans" + ), + 10, + )?; + + // ------------------- Step 6. ------------------- + let pfm_memo = packet_forward_memo( + find_cosmos_address(&test_gaia_2, COSMOS_USER)?.into(), + &port_id_namada, + &channel_id_namada_2, + Some(Duration::new(1, 0)), + None, + ); + // Stop Hermes for timeout test + let mut hermes_2 = bg_hermes_2.foreground(); + hermes_2.interrupt()?; + + transfer_from_cosmos( + &test_gaia_1, + COSMOS_USER, + &namada_receiver, + format!( + "transfer/{channel_id_gaia_1}/transfer/{channel_id_namada_2}/\ + samoleans" + ), + 10, + &port_id_gaia_1, + &channel_id_gaia_1, + Some(Either::Right(pfm_memo)), + None, + )?; + + // wait for the timeout + sleep(10); + + // Restart relaying + let hermes_2 = run_hermes(&hermes_namada_gaia2)?; + let _bg_hermes_2 = hermes_2.background(); + + wait_for_packet_relay( + &hermes_gaia1_namada, + &port_id_namada, + &channel_id_namada_1, + &test, + )?; + wait_for_packet_relay( + &hermes_namada_gaia2, + &port_id_gaia_2, + &channel_id_gaia_2, + &test_gaia_2, + )?; + + // Check the non-native samoleans have been refunded on the first chain + check_cosmos_balance( + &test_gaia_1, + COSMOS_USER, + format!( + "transfer/{channel_id_gaia_1}/transfer/{channel_id_namada_2}/\ + samoleans" + ), + 10, + )?; + Ok(()) +} + +/// Test that we are able to use the shielded-receive +/// middleware to shield funds specified in the memo +/// message. +#[test] +fn ibc_shielded_recv_middleware_happy_flow() -> Result<()> { + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1800); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia, test, test_gaia) = + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; + let _bg_ledger = ledger.background(); + let _bg_gaia = gaia.background(); + sleep(5); + + let hermes_dir = setup_hermes(&test, &test_gaia)?; + let port_id_namada = FT_PORT_ID.parse().unwrap(); + let port_id_gaia = FT_PORT_ID.parse().unwrap(); + let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, + &test, + &test_gaia, + &port_id_namada, + &port_id_gaia, + )?; + + // Start relaying + let hermes = run_hermes(&hermes_dir)?; + let _bg_hermes = hermes.background(); + + // 1. Shield 10 NAM to AA_PAYMENT_ADDRESS + transfer_on_chain( + &test, + "shield", + ALBERT, + AA_PAYMENT_ADDRESS, + NAM, + 10, + ALBERT_KEY, + &[], + )?; + check_shielded_balance(&test, AA_VIEWING_KEY, NAM, 10)?; + + // 2. Unshield from A_SPENDING_KEY to B_SPENDING_KEY, + // using the packet forward and shielded receive + // middlewares + let nam_addr = find_address(&test, NAM)?; + let overflow_addr = "tnam1qrqzqa0l0rzzrlr20n487l6n865t8ndv6uhseulq"; + let ibc_denom_on_gaia = format!("transfer/{channel_id_gaia}/{nam_addr}"); + let memo_path = gen_ibc_shielding_data( + &test, + AB_PAYMENT_ADDRESS, + &ibc_denom_on_gaia, + 8, + &port_id_namada, + &channel_id_namada, + )?; + let memo = packet_forward_memo( + MASP.to_string().into(), + &PortId::transfer(), + &channel_id_namada, + None, + Some(shielded_recv_memo_value( + &memo_path, + Amount::native_whole(8), + overflow_addr.parse().unwrap(), + )), + ); + transfer( + &test, + A_SPENDING_KEY, + "PacketForwardMiddleware", + NAM, + 10, + Some(ALBERT_KEY), + &PortId::transfer(), + &channel_id_namada, + None, + None, + None, + Some(&memo), + true, + )?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; + + // Check the token on Namada + check_shielded_balance(&test, AA_VIEWING_KEY, NAM, 0)?; + check_shielded_balance(&test, AB_VIEWING_KEY, NAM, 8)?; + check_balance(&test, overflow_addr, NAM, 2)?; + + Ok(()) +} + +/// Test that if the received amount underflows the minimum +/// amount, we error out and refund assets. +#[test] +fn ibc_shielded_recv_middleware_unhappy_flow() -> Result<()> { + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1800); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (ledger, gaia, test, test_gaia) = + run_namada_cosmos(CosmosChainType::Gaia(None), update_genesis)?; + let _bg_ledger = ledger.background(); + let _bg_gaia = gaia.background(); + sleep(5); + + let hermes_dir = setup_hermes(&test, &test_gaia)?; + let port_id_namada = FT_PORT_ID.parse().unwrap(); + let port_id_gaia = FT_PORT_ID.parse().unwrap(); + let (channel_id_namada, channel_id_gaia) = create_channel_with_hermes( + &hermes_dir, + &test, + &test_gaia, + &port_id_namada, + &port_id_gaia, + )?; + + // Start relaying + let hermes = run_hermes(&hermes_dir)?; + let _bg_hermes = hermes.background(); + + let nam_addr = find_address(&test, NAM)?; + let overflow_addr = "tnam1qrqzqa0l0rzzrlr20n487l6n865t8ndv6uhseulq"; + let ibc_denom_on_gaia = format!("transfer/{channel_id_gaia}/{nam_addr}"); + check_balance(&test, ALBERT, NAM, 2_000_000)?; + + let memo_path = gen_ibc_shielding_data( &test, - ALBERT, - &gaia_receiver, - NAM, - 1, - Some(ALBERT_KEY), + AB_PAYMENT_ADDRESS, + &ibc_denom_on_gaia, + 8, &port_id_namada, &channel_id_namada, - None, - None, - // expect an error of the throughput limit - Some( - "Transfer exceeding the per-epoch throughput limit is not allowed", - ), - false, )?; - - // wait for the next epoch - let mut epoch = get_epoch(&test, &rpc).unwrap(); - let next_epoch = epoch.next(); - while epoch <= next_epoch { - epoch = epoch_sleep(&test, &rpc, 120)?; - } - - // Transfer 1 NAM from Namada to Gaia will succeed in the new epoch + let memo = packet_forward_memo( + MASP.to_string().into(), + &PortId::transfer(), + &channel_id_namada, + None, + Some(shielded_recv_memo_value( + &memo_path, + Amount::native_whole(8), + overflow_addr.parse().unwrap(), + )), + ); transfer( &test, ALBERT, - &gaia_receiver, + "PacketForwardMiddleware", NAM, - 1, + 7, Some(ALBERT_KEY), - &port_id_namada, + &PortId::transfer(), &channel_id_namada, None, None, None, + Some(&memo), false, )?; + wait_for_packet_relay(&hermes_dir, &port_id_gaia, &channel_id_gaia, &test)?; - // wait for the next epoch - let mut epoch = get_epoch(&test, &rpc).unwrap(); - let next_epoch = epoch.next(); - while epoch <= next_epoch { - sleep(5); - epoch = get_epoch(&test, &rpc).unwrap(); - } - - // Transfer 2 samoleans from Gaia to Namada will succeed, but Namada can't - // receive due to the mint limit and the packet will be timed out - let namada_receiver = find_address(&test, ALBERT)?.to_string(); - transfer_from_cosmos( - &test_gaia, - COSMOS_USER, - namada_receiver, - COSMOS_COIN, - 2, - &port_id_gaia, - &channel_id_gaia, - None, - Some(Duration::new(10, 0)), - )?; - wait_for_packet_relay(&port_id_gaia, &channel_id_gaia, &test)?; - - // Check if Namada hasn't receive it - let ibc_denom = - format!("{port_id_namada}/{channel_id_namada}/{COSMOS_COIN}"); - // Need the raw address to check the balance because the token shouldn't be - // received - let token_addr = ibc_token(ibc_denom).to_string(); - check_balance(&test, ALBERT, token_addr, 0)?; + // Check the token on Namada + check_balance(&test, ALBERT, NAM, 2_000_000)?; + check_shielded_balance(&test, AB_VIEWING_KEY, NAM, 0)?; + check_balance(&test, overflow_addr, NAM, 0)?; Ok(()) } @@ -1132,15 +2140,21 @@ fn run_namada_cosmos( let ledger = start_namada_ledger_node_wait_wasm(&test, Some(0), Some(40))?; - // Cosmos - let test_cosmos = setup_cosmos(chain_type)?; - let cosmos = run_cosmos(&test_cosmos)?; - sleep(5); + let (cosmos, test_cosmos) = setup_and_boot_cosmos(chain_type)?; Ok((ledger, cosmos, test, test_cosmos)) } +fn setup_and_boot_cosmos( + chain_type: CosmosChainType, +) -> Result<(NamadaCmd, Test)> { + let test_cosmos = setup_cosmos(chain_type)?; + let cosmos = run_cosmos(&test_cosmos, true)?; + Ok((cosmos, test_cosmos)) +} + fn create_channel_with_hermes( + hermes_dir: &TestDir, test_a: &Test, test_b: &Test, port_id_a: &PortId, @@ -1153,6 +2167,7 @@ fn create_channel_with_hermes( } else { FT_CHANNEL_VERSION }; + let args = [ "create", "channel", @@ -1170,7 +2185,7 @@ fn create_channel_with_hermes( "--yes", ]; - let mut hermes = run_hermes_cmd(test_a, args, Some(240))?; + let mut hermes = run_hermes_cmd(hermes_dir, args, Some(240))?; let (channel_id_a, channel_id_b) = get_channel_ids_from_hermes_output(&mut hermes)?; hermes.assert_success(); @@ -1192,35 +2207,32 @@ fn get_channel_ids_from_hermes_output( Ok((channel_id_a, channel_id_b)) } -fn run_hermes(test: &Test) -> Result { +fn run_hermes(hermes_dir: &TestDir) -> Result { let args = ["start"]; - let mut hermes = run_hermes_cmd(test, args, Some(40))?; + let mut hermes = run_hermes_cmd(hermes_dir, args, Some(40))?; hermes.exp_string("Hermes has started")?; Ok(hermes) } -fn run_cosmos(test: &Test) -> Result { +fn run_cosmos(test: &Test, kill: bool) -> Result { let chain_type = CosmosChainType::chain_type(test.net.chain_id.as_str())?; let cmd_path = chain_type.command_path(); // cosmos process is sometimes left lingering causing subsequent runs to // fail - std::process::Command::new("pkill") - .args(["-9", cmd_path]) - .output() - .unwrap(); - - let args = [ - "start", - "--pruning", - "nothing", - "--grpc.address", - "0.0.0.0:9090", - ]; + if kill { + std::process::Command::new("pkill") + .args(["-9", cmd_path]) + .output() + .unwrap(); + } + let port_arg = format!("0.0.0.0:{}", chain_type.get_grpc_port_number()); + let args = ["start", "--pruning", "nothing", "--grpc.address", &port_arg]; let cosmos = run_cosmos_cmd(test, args, Some(40))?; Ok(cosmos) } fn wait_for_packet_relay( + hermes_dir: &TestDir, port_id: &PortId, channel_id: &ChannelId, test: &Test, @@ -1239,7 +2251,7 @@ fn wait_for_packet_relay( ]; for _ in 0..10 { sleep(10); - let mut hermes = run_hermes_cmd(test, args, Some(40))?; + let mut hermes = run_hermes_cmd(hermes_dir, args, Some(40))?; // Check no pending packet if hermes .exp_string( @@ -1260,6 +2272,7 @@ fn wait_for_packet_relay( } fn clear_packet( + hermes_dir: &TestDir, port_id: &PortId, channel_id: &ChannelId, test: &Test, @@ -1274,14 +2287,14 @@ fn clear_packet( "--channel", channel_id.as_str(), ]; - let mut hermes = run_hermes_cmd(test, args, Some(40))?; + let mut hermes = run_hermes_cmd(hermes_dir, args, Some(40))?; hermes.assert_success(); Ok(()) } fn upgrade_client( - test: &Test, + hermes_dir: &TestDir, host_chain_id: impl AsRef, upgrade_height: u64, ) -> Result<()> { @@ -1295,7 +2308,7 @@ fn upgrade_client( "--upgrade-height", &upgrade_height.to_string(), ]; - let mut hermes = run_hermes_cmd(test, args, Some(120))?; + let mut hermes = run_hermes_cmd(hermes_dir, args, Some(120))?; hermes.exp_string("upgraded-chain")?; hermes.assert_success(); @@ -1352,6 +2365,7 @@ fn try_invalid_transfers( None, // the IBC denom can't be parsed when using an invalid port Some(&format!("Invalid IBC denom: {nam_addr}")), + None, false, )?; @@ -1368,6 +2382,7 @@ fn try_invalid_transfers( None, None, Some("IBC token transfer error: context error: `ICS04 Channel error"), + None, false, )?; @@ -1423,6 +2438,7 @@ fn transfer( timeout_sec: Option, shielding_data_path: Option, expected_err: Option<&str>, + ibc_memo: Option<&str>, gen_refund_target: bool, ) -> Result { let rpc = get_actor_rpc(test, Who::Validator(0)); @@ -1450,6 +2466,10 @@ fn transfer( &rpc, ]); + if let Some(ibc_memo) = ibc_memo { + tx_args.extend_from_slice(&["--ibc-memo", ibc_memo]); + } + if let Some(signer) = signer { tx_args.extend_from_slice(&["--signing-keys", signer]); } else { @@ -1674,7 +2694,7 @@ fn propose_upgrade_client( let proposal_json_path = test_gaia.test_dir.path().join("proposal.json"); write_json_file(proposal_json_path.as_path(), proposal_json); - let rpc = format!("tcp://{COSMOS_RPC}"); + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test_gaia)); let submit_proposal_args = vec![ "tx", "gov", @@ -1744,7 +2764,8 @@ fn propose_gas_token(test: &Test) -> Result { } fn wait_for_pass(test: &Test) -> Result<()> { - let args = ["query", "gov", "proposal", "1"]; + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); + let args = ["query", "gov", "proposal", "1", "--node", &rpc]; for _ in 0..10 { sleep(5); let mut gaia = run_cosmos_cmd(test, args, Some(40))?; @@ -1757,7 +2778,7 @@ fn wait_for_pass(test: &Test) -> Result<()> { } fn vote_on_gaia(test: &Test) -> Result<()> { - let rpc = format!("tcp://{COSMOS_RPC}"); + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); let args = vec![ "tx", "gov", @@ -1833,13 +2854,15 @@ fn transfer_from_cosmos( amount: u64, port_id: &PortId, channel_id: &ChannelId, - memo_path: Option, + memo: Option>, timeout_sec: Option, ) -> Result<()> { let port_id = port_id.to_string(); let channel_id = channel_id.to_string(); let amount = format!("{}{}", amount, token.as_ref()); - let rpc = format!("tcp://{COSMOS_RPC}"); + let chain_type = + CosmosChainType::chain_type(test.net.chain_id.as_str()).unwrap(); + let rpc = format!("tcp://127.0.0.1:{}", chain_type.get_rpc_port_number()); // If the receiver is a pyament address we want to mask it to the more // general MASP internal address to improve on privacy let receiver = match PaymentAddress::from_str(receiver.as_ref()) { @@ -1867,13 +2890,16 @@ fn transfer_from_cosmos( "--yes", ]; - let memo = memo_path - .as_ref() - .map(|path| { - std::fs::read_to_string(path).expect("Reading memo file failed") + let is_memo = memo.is_some(); + let memo = memo + .map(|m| match m { + Either::Left(path) => { + std::fs::read_to_string(path).expect("Reading memo file failed") + } + Either::Right(memo) => memo, }) .unwrap_or_default(); - if memo_path.is_some() { + if is_memo { args.push("--memo"); args.push(&memo); } @@ -1914,7 +2940,10 @@ fn check_tx_height(test: &Test, client: &mut NamadaCmd) -> Result { } fn query_height(test: &Test) -> Result { - let rpc = get_actor_rpc(test, Who::Validator(0)); + let rpc = match CosmosChainType::chain_type(test.net.chain_id.as_str()) { + Ok(_) => format!("http://{}", get_cosmos_rpc_address(test)), + Err(_) => get_actor_rpc(test, Who::Validator(0)), + }; let tendermint_url = Url::from_str(&rpc).unwrap(); let client = HttpClient::new(tendermint_url).unwrap(); @@ -1992,14 +3021,10 @@ fn check_cosmos_balance( expected_amount: u64, ) -> Result<()> { let addr = find_cosmos_address(test, owner)?; - let args = [ - "query", - "bank", - "balances", - &addr, - "--node", - &format!("tcp://{COSMOS_RPC}"), - ]; + let chain_type = + CosmosChainType::chain_type(test.net.chain_id.as_str()).unwrap(); + let rpc = format!("tcp://127.0.0.1:{}", chain_type.get_rpc_port_number()); + let args = ["query", "bank", "balances", &addr, "--node", &rpc]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; cosmos.exp_string(&format!("amount: \"{expected_amount}\""))?; let expected_denom = if denom.as_ref().contains('/') { @@ -2098,9 +3123,9 @@ fn initialize_nft_contracts(test: &Test) -> Result<(String, String)> { Ok(dir) => PathBuf::from(dir), Err(_) => working_dir(), }; - let rpc = format!("tcp://{COSMOS_RPC}"); + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); let minter_addr = find_cosmos_address(test, COSMOS_USER)?; - + sleep(5); // Store cw721 contract let cw721_wasm_path = contract_dir.join(CW721_WASM).to_string_lossy().to_string(); @@ -2127,7 +3152,7 @@ fn initialize_nft_contracts(test: &Test) -> Result<(String, String)> { ]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; cosmos.exp_eof()?; - sleep(1); + sleep(5); // Store ics721 contract let ics721_wasm_path = @@ -2157,10 +3182,6 @@ fn initialize_nft_contracts(test: &Test) -> Result<(String, String)> { cosmos.exp_eof()?; sleep(1); - let args = vec!["query", "wasm", "list-code"]; - let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; - cosmos.assert_success(); - // Instantiate cw721 contract let json = serde_json::json!({ "name": "test_nft", @@ -2229,17 +3250,32 @@ fn initialize_nft_contracts(test: &Test) -> Result<(String, String)> { ]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; cosmos.exp_eof()?; + sleep(1); // Check the CW721 contract - let args = vec!["query", "wasm", "list-contract-by-code", "1"]; + let args = vec![ + "query", + "wasm", + "list-contract-by-code", + "1", + "--node", + &rpc, + ]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; let (_unread, matched) = cosmos.exp_regex("wasm.*")?; let cw721_contract = matched.trim().to_string(); cosmos.exp_eof()?; // Check the ICS721 contract - let args = vec!["query", "wasm", "list-contract-by-code", "2"]; + let args = vec![ + "query", + "wasm", + "list-contract-by-code", + "2", + "--node", + &rpc, + ]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; let (_unread, matched) = cosmos.exp_regex("wasm.*")?; let ics721_contract = matched.trim().to_string(); @@ -2249,8 +3285,10 @@ fn initialize_nft_contracts(test: &Test) -> Result<(String, String)> { } fn get_cosmwasm_port_id(test: &Test, ics721_contract: &str) -> Result { + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); // Get the port ID - let args = vec!["query", "wasm", "contract", ics721_contract]; + let args = + vec!["query", "wasm", "contract", ics721_contract, "--node", &rpc]; let mut cosmos = run_cosmos_cmd(test, args, Some(40))?; let (_unread, matched) = cosmos.exp_regex("ibc_port_id: wasm.*")?; let port_id = matched.trim().split(' ').last().expect("invalid output"); @@ -2266,7 +3304,7 @@ fn mint_nft( minter_addr: &str, token_id: &str, ) -> Result<()> { - let rpc = format!("tcp://{COSMOS_RPC}"); + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); // Mint an NFT let json = serde_json::json!({ @@ -2319,7 +3357,7 @@ fn nft_transfer_from_cosmos( ) -> Result<()> { let channel_id = channel_id.to_string(); let timeout_height = timeout_height.unwrap_or(100000); - let rpc = format!("tcp://{COSMOS_RPC}"); + let rpc = format!("tcp://{}", get_cosmos_rpc_address(test)); let receiver = match PaymentAddress::from_str(receiver.as_ref()) { Ok(_) => MASP.to_string(), @@ -2382,3 +3420,577 @@ fn nft_transfer_from_cosmos( Ok(()) } + +/// Create a packet forward memo and serialize it +fn packet_forward_memo( + receiver: Signer, + port_id: &PortId, + channel_id: &ChannelId, + timeout: Option, + next: Option>, +) -> String { + serde_json::to_string(&serde_json::Value::Object( + packet_forward_memo_value(receiver, port_id, channel_id, timeout, next), + )) + .expect("Test failed") +} + +fn packet_forward_memo_value( + receiver: Signer, + port_id: &PortId, + channel_id: &ChannelId, + timeout: Option, + next: Option>, +) -> serde_json::Map { + let value = + serde_json::to_value(&ibc_middleware_packet_forward::PacketMetadata { + forward: ForwardMetadata { + receiver, + port: port_id.clone(), + channel: channel_id.clone(), + timeout: timeout.map(|t| { + ibc_middleware_packet_forward::Duration::from_dur( + dur::Duration::from_std(t), + ) + }), + retries: Some(0), + next, + }, + }) + .expect("Test failed"); + + if let serde_json::Value::Object(memo) = value { + memo + } else { + unreachable!() + } +} + +fn shielded_recv_memo_value( + masp_transfer_path: &Path, + shielded_amount: Amount, + overflow_receiver: namada_core::address::Address, +) -> serde_json::Map { + use namada_core::string_encoding::StringEncoded; + use namada_sdk::ibc::{NamadaMemo, NamadaMemoData}; + + let transfer = + std::fs::read_to_string(masp_transfer_path).expect("Test failed"); + let tx = StringEncoded::new( + IbcShieldingData::from_str(&transfer).expect("Test failed"), + ); + let data = NamadaMemoData::OsmosisSwap { + shielding_data: tx, + shielded_amount, + overflow_receiver, + }; + + let value = serde_json::to_value(&NamadaMemo { namada: data }) + .expect("Test failed"); + + if let serde_json::Value::Object(memo) = value { + memo + } else { + unreachable!() + } +} + +/// Basic Osmosis test that checks if the chain has been set up correctly. +#[test] +fn osmosis_basic() -> Result<()> { + let (osmosis, test_osmosis) = + setup_and_boot_cosmos(CosmosChainType::Osmosis)?; + + let _bg_osmosis = osmosis.background(); + sleep(5); + + check_cosmos_balance(&test_osmosis, COSMOS_USER, COSMOS_COIN, 1_000)?; + + Ok(()) +} + +#[test] +fn osmosis_bingbong() -> Result<()> { + // Set up a big Cosmos party + let update_genesis = + |mut genesis: templates::All, base_dir: &_| { + genesis.parameters.parameters.epochs_per_year = + epochs_per_year_from_min_duration(1800); + genesis.parameters.ibc_params.default_mint_limit = + Amount::max_signed(); + genesis + .parameters + .ibc_params + .default_per_epoch_throughput_limit = Amount::max_signed(); + setup::set_validators(1, genesis, base_dir, |_| 0, vec![]) + }; + let (namada, gaia, test_namada, test_gaia) = + run_namada_cosmos(CosmosChainType::Gaia(Some(1)), update_genesis)?; + + let (osmosis, test_osmosis) = + setup_and_boot_cosmos(CosmosChainType::Osmosis)?; + + let _bg_osmosis = osmosis.background(); + let _bg_ledger = namada.background(); + let _bg_gaia = gaia.background(); + + // The MC of the party + let osmosis_jones = find_cosmos_address(&test_osmosis, COSMOS_USER)?; + + // Everyone shall take a 5 second nap, partied too hard + // (big up MC Osmosis Jones) + sleep(5); + + // Create hermes relayers with the following config: + // ================================================= + // + // namada -- osmosis -- gaia + // \_____________________/ + + // Set up initial hermes configs + let hermes_gaia_namada = setup_hermes(&test_gaia, &test_namada).map_err(|_| { + eyre!("failed to join thread hermes_gaia_namada") + })?; + let hermes_gaia_osmosis = setup_hermes(&test_gaia, &test_osmosis) + .map_err(|_| { + eyre!("failed to join thread hermes_gaia_osmosis") + })?; + let hermes_namada_osmosis = setup_hermes(&test_namada, &test_osmosis) + .map_err(|_| { + eyre!("failed to join thread hermes_namada_osmosis") + })?; + std::thread::sleep(Duration::from_secs(5)); + // Set up channels + let ( + channel_from_gaia_to_namada, + channel_from_namada_to_gaia, + ) = create_channel_with_hermes( + &hermes_gaia_namada, + &test_gaia, + &test_namada, + &PortId::transfer(), + &PortId::transfer(), + )?; + + // Osmosis currently uses an older version of the Cosmos SDK + // that will error if txs are sent too close together. See + // https://github.com/cosmos/cosmos-sdk/issues/13621 + std::thread::sleep(Duration::from_secs(5)); + let ( + channel_from_gaia_to_osmosis, + channel_from_osmosis_to_gaia, + ) = create_channel_with_hermes( + &hermes_gaia_osmosis, + &test_gaia, + &test_osmosis, + &PortId::transfer(), + &PortId::transfer(), + )?; + + + let ( + + channel_from_namada_to_osmosis, + channel_from_osmosis_to_namada, + ) = create_channel_with_hermes( + &hermes_namada_osmosis, + &test_namada, + &test_osmosis, + &PortId::transfer(), + &PortId::transfer(), + )?; + + + // Start relaying + let hermes_1 = run_hermes(&hermes_gaia_namada)?; + let hermes_2 = run_hermes(&hermes_gaia_osmosis)?; + let hermes_3 = run_hermes(&hermes_namada_osmosis)?; + let _bg_hermes_1 = hermes_1.background(); + let _bg_hermes_2 = hermes_2.background(); + let _bg_hermes_3 = hermes_3.background(); + + // Transfer assets to Osmosis, in order to create pools + let rpc_namada = get_actor_rpc(&test_namada, Who::Validator(0)); + let rpc_gaia = format!("tcp://{}", get_cosmos_rpc_address(&test_gaia)); + + // Transfer NAM from Namada + transfer( + &test_namada, + ALBERT, + &osmosis_jones, + NAM, + 500, + Some(ALBERT_KEY), + &PortId::transfer(), + &channel_from_namada_to_osmosis, + None, + None, + None, + None, + false, + )?; + // Transfer Samoleans from Gaia + transfer_from_cosmos( + &test_gaia, + COSMOS_USER, + &osmosis_jones, + COSMOS_COIN, + 500, + &PortId::transfer(), + &channel_from_gaia_to_osmosis, + None, + None, + )?; + + // Related to the issue above. In general, it takes osmosis some time + // to update state. Thus calls to it should be spaced out accordingly. + std::thread::sleep(Duration::from_secs(5)); + // Check balance of transferred assets on Osmosis + let nam_token_addr = find_address(&test_namada, NAM)?; + check_cosmos_balance( + &test_osmosis, + COSMOS_USER, + &format!("transfer/{channel_from_osmosis_to_namada}/{nam_token_addr}"), + 500_000_000, + )?; + + check_cosmos_balance( + &test_osmosis, + COSMOS_USER, + &format!("transfer/{channel_from_osmosis_to_gaia}/{COSMOS_COIN}"), + 500, + )?; + + // Set up contracts on Osmosis + let rpc_osmosis = format!("tcp://{}", get_cosmos_rpc_address(&test_osmosis)); + + const CROSSCHAIN_REGISTRY_CODE_ID: &str = "1"; + const SWAPROUTER_CODE_ID: &str = "2"; + const CROSSCHAIN_SWAPS_CODE_ID: &str = "3"; + + const CROSSCHAIN_REGISTRY_SHA256_HASH: &str = + "72a4edfebadbe25593ea06de790592b9a85ea80c2e5183e5c46113dc5208b962"; + const SWAPROUTER_CODE_SHA256_HASH: &str = + "01dd680a41099846dafb80935df2e03a60cc4d910cb258f7a014e98d3e70c3b7"; + const CROSSCHAIN_SWAPS_SHA256_HASH: &str = + "0107bae644b60f03be67bd135d488d05c4ade0b5ba15f9ab1dc2f70197c968b4"; + + // Deploy each contract's wasm bytecode + for wasm in [ + "crosschain_registry.wasm", + "swaprouter.wasm", + "crosschain_swaps.wasm", + ] { + let wasm_path = osmosis_fixtures_dir().join("wasm_bytecode").join(wasm); + let wasm_path_str = wasm_path.to_string_lossy(); + std::thread::sleep(Duration::from_secs(5)); + let args = cosmos_common_args( + "5000000", + None, + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec!["tx", "wasm", "store", &wasm_path_str], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + } + + // Instantiate `crosschain_registry.wasm` + let json = format!(r#"{{"owner":"{osmosis_jones}"}}"#); + std::thread::sleep(Duration::from_secs(5)); + let crosschain_registry_addr = build_contract_addr( + &test_osmosis, + CROSSCHAIN_REGISTRY_SHA256_HASH, + &osmosis_jones, + )?; + std::thread::sleep(Duration::from_secs(5)); + let args = cosmos_common_args( + "2500000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "instantiate2", + CROSSCHAIN_REGISTRY_CODE_ID, + &json, + CONTRACT_SALT_HEX, + "--label", + "CrosschainRegistry", + "--no-admin", + "--hex", + ], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + + // Instantiate `swaprouter.wasm` + let json = format!(r#"{{"owner":"{osmosis_jones}"}}"#); + std::thread::sleep(Duration::from_secs(10)); + let swaprouter_addr = build_contract_addr( + &test_osmosis, + SWAPROUTER_CODE_SHA256_HASH, + &osmosis_jones, + )?; + std::thread::sleep(Duration::from_secs(10)); + let args = cosmos_common_args( + "250000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "instantiate2", + SWAPROUTER_CODE_ID, + &json, + CONTRACT_SALT_HEX, + "--label", + "SwapRouter", + "--no-admin", + "--hex", + ], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + + // Instantiate `swaprouter.wasm` + std::thread::sleep(Duration::from_secs(10)); + let json = format!( + r#"{{"governor":"{osmosis_jones}","swap_contract":"{swaprouter_addr}","registry_contract":"{crosschain_registry_addr}"}}"# + ); + let _crosschain_swaps_addr = build_contract_addr( + &test_osmosis, + CROSSCHAIN_SWAPS_SHA256_HASH, + &osmosis_jones, + )?; + std::thread::sleep(Duration::from_secs(10)); + let args = cosmos_common_args( + "500000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "instantiate2", + CROSSCHAIN_SWAPS_CODE_ID, + &json, + CONTRACT_SALT_HEX, + "--label", + "CrosschainSwaps", + "--no-admin", + "--hex", + ], + ); + + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + std::thread::sleep(Duration::from_secs(5)); + // Modify the bech32 prefixes + let msg = serde_json::to_string(&json!({ + "modify_bech32_prefixes": { + "operations": [ + { + "operation": "set", + "chain_name": "namada", + "prefix": "tnam" + }, + { + "operation": "set", + "chain_name": "osmosis", + "prefix": "osmo" + }, + { + "operation": "set", + "chain_name": "gaia", + "prefix": "cosmo" + } + ] + }})).unwrap(); + + let args = cosmos_common_args( + "5000000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "execute", + &crosschain_registry_addr, + &msg, + ], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + std::thread::sleep(Duration::from_secs(5)); + + // Modify the channel chain links + let msg = serde_json::to_string(&json!({ + "modify_chain_channel_links": { + "operations": [ + { + "operation": "set", + "source_chain": "namada", + "destination_chain": "osmosis", + "channel_id": channel_from_namada_to_osmosis + }, + { + "operation": "set", + "source_chain": "osmosis", + "destination_chain": "namada", + "channel_id": channel_from_osmosis_to_namada + }, + { + "operation": "set", + "source_chain": "namada", + "destination_chain": "gaia", + "channel_id": channel_from_namada_to_gaia + }, + { + "operation": "set", + "source_chain": "gaia", + "destination_chain": "namada", + "channel_id": channel_from_gaia_to_namada + }, + { + "operation": "set", + "source_chain": "gaia", + "destination_chain": "osmosis", + "channel_id": channel_from_gaia_to_osmosis + }, + { + "operation": "set", + "source_chain": "osmosis", + "destination_chain": "gaia", + "channel_id": channel_from_osmosis_to_gaia + } + ] + }})).unwrap(); + let args = cosmos_common_args( + "5000000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "execute", + &crosschain_registry_addr, + &msg + ], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + std::thread::sleep(Duration::from_secs(5)); + // enable pfm on gaia and namada + for (chain, token) in [ + ("namada", get_gaia_denom_hash(format!("transfer/{channel_from_osmosis_to_namada}/{nam_token_addr}"))), + ("gaia", get_gaia_denom_hash(format!("transfer/{channel_from_osmosis_to_gaia}/{COSMOS_COIN}"))), + ] { + let msg = format!(r#"{{"propose_pfm": {{"chain": "{chain}"}}}}"#); + let amount = format!("1{token}"); + let args = cosmos_common_args( + "5000000", + Some("0.01stake"), + test_osmosis.net.chain_id.as_str(), + &rpc_osmosis, + vec![ + "tx", + "wasm", + "execute", + &crosschain_registry_addr, + &msg, + "--amount", + &amount, + ], + ); + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.assert_success(); + std::thread::sleep(Duration::from_secs(5)); + } + + wait_for_packet_relay( + &hermes_namada_osmosis, + &PortId::transfer(), + &channel_from_osmosis_to_namada, + &test_osmosis + )?; + wait_for_packet_relay( + &hermes_gaia_osmosis, + &PortId::transfer(), + &channel_from_osmosis_to_gaia, + &test_osmosis + )?; + + for chain in ["namada", "gaia"] { + let msg = format!(r#"{{"has_packet_forwarding": {{"chain": "{chain}"}}}}"#); + let args = + vec![ + "query", + "wasm", + "contract-state", + "smart", + &crosschain_registry_addr, + &msg + ]; + let mut osmosis_cmd = run_cosmos_cmd(&test_osmosis, args, Some(40))?; + osmosis_cmd.exp_string("data: true")?; + std::thread::sleep(Duration::from_secs(5)); + } + + Ok(()) +} + +fn cosmos_common_args<'a>( + gas: &'a str, + gas_price: Option<&'a str>, + chain_id: &'a str, + rpc: &'a str, + mut args: Vec<&'a str>, +) -> Vec<&'a str> { + args.extend_from_slice(&[ + "--from", + COSMOS_USER, + "--gas", + gas, + "--gas-prices", + gas_price.unwrap_or("0.01stake"), + "--node", + rpc, + "--keyring-backend", + "test", + "--chain-id", + chain_id, + "--yes", + ]); + args +} + +fn build_contract_addr( + test: &Test, + wasm_bytecode_hash: &str, + creator_addr: &str, +) -> Result { + let osmosis_home = test.test_dir.as_ref().join("osmosis"); + let args = [ + "--home", + osmosis_home.to_str().unwrap(), + "query", + "wasm", + "build-address", + wasm_bytecode_hash, + creator_addr, + CONTRACT_SALT_HEX, + ]; + let mut cosmos = run_cosmos_cmd_homeless(test, args, Some(40))?; + let regex = format!("address: .*\n"); + let (_, matched) = cosmos.exp_regex(®ex)?; + let mut parts = matched.split(':'); + parts.next().unwrap(); + Ok(parts.next().unwrap().trim().to_string()) +} + +const CONTRACT_SALT_HEX: &str = "01020304"; diff --git a/crates/tests/src/e2e/setup.rs b/crates/tests/src/e2e/setup.rs index d7c9a663e9..bad82e23e2 100644 --- a/crates/tests/src/e2e/setup.rs +++ b/crates/tests/src/e2e/setup.rs @@ -41,10 +41,7 @@ use rand::rngs::OsRng; use rand::Rng; use tempfile::{tempdir, tempdir_in, TempDir}; -use crate::e2e::helpers::{ - find_cosmos_address, generate_bin_command, make_hermes_config, - update_cosmos_config, -}; +use crate::e2e::helpers::{find_cosmos_address, generate_bin_command, get_cosmos_rpc_address, make_hermes_config, update_cosmos_config}; /// For `color_eyre::install`, which fails if called more than once in the same /// process @@ -844,6 +841,22 @@ pub fn working_dir() -> PathBuf { working_dir } +/// Return the path to all test fixture. +pub fn fixtures_dir() -> PathBuf { + let mut dir = working_dir(); + dir.push("crates"); + dir.push("tests"); + dir.push("fixtures"); + dir +} + +/// Return the path to all osmosis fixture. +pub fn osmosis_fixtures_dir() -> PathBuf { + let mut dir = fixtures_dir(); + dir.push("osmosis_data"); + dir +} + /// A command under test pub struct NamadaCmd { pub session: Session>, @@ -1123,8 +1136,8 @@ where // Root cargo workspace manifest path let (bin_name, log_level) = match bin { - Bin::Namada => ("namada", "info"), - Bin::Node => ("namadan", "info"), + Bin::Namada => ("namada", "debug"), + Bin::Node => ("namadan", "debug"), Bin::Client => ( "namadac", if is_shielded_sync { @@ -1239,36 +1252,121 @@ pub fn sleep(seconds: u64) { thread::sleep(time::Duration::from_secs(seconds)); } -pub fn setup_hermes(test_a: &Test, test_b: &Test) -> Result<()> { +pub fn setup_hermes(test_a: &Test, test_b: &Test) -> Result { + let hermes_dir = TestDir::new(); + println!("\n{}", "Setting up Hermes".underline().green(),); + let chain_name_a = CosmosChainType::chain_type(test_a.net.chain_id.as_str()) + .map(|c| c.chain_id()).ok(); + let chain_name_b = CosmosChainType::chain_type(test_b.net.chain_id.as_str()) + .map(|c| c.chain_id()).ok(); + let relayer = chain_name_a.zip(chain_name_b) + .map(|(a, b)| format!("{a}_{b}_relayer")); + make_hermes_config(&hermes_dir, test_a, test_b, relayer.as_ref().map(|s| s.as_ref()))?; - make_hermes_config(test_a, test_b)?; for test in [test_a, test_b] { let chain_id = test.net.chain_id.as_str(); let chain_dir = test.test_dir.as_ref().join(chain_id); - let key_file_path = match CosmosChainType::chain_type(chain_id) { - Ok(_) => chain_dir - .join(format!("{}_seed.json", constants::COSMOS_RELAYER)), - Err(_) => wallet::wallet_file(chain_dir), + match CosmosChainType::chain_type(chain_id) { + Ok(_) => { + if let Some(relayer) = relayer.as_ref() { + // we create a new relayer for each ibc connection between to non-Namada chains + let key_file = chain_dir.join(format!("{relayer}_seed.json")); + let args = [ + "keys", + "add", + &relayer, + "--keyring-backend", + "test", + "--output", + "json", + ]; + let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; + let result = cosmos.exp_string("\n")?; + let mut file = File::create(&key_file).unwrap(); + file.write_all(result.as_bytes()).map_err(|e| { + eyre!(format!("Writing a Cosmos key file failed: {}", e)) + })?; + + let account = find_cosmos_address(test, &relayer)?; + // Add tokens to the new relayer account + let args = [ + "tx", + "bank", + "send", + constants::COSMOS_RELAYER, + &account, + "500000stake", + "--from", + constants::COSMOS_RELAYER, + "--gas", + "250000", + "--gas-prices", + "0.01stake", + "--node", + &format!("http://{}", get_cosmos_rpc_address(&test)), + "--keyring-backend", + "test", + "--chain-id", + chain_id, + "--yes", + ]; + + let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; + cosmos.assert_success(); + + // add to hermes + let args = [ + "keys", + "add", + "--chain", + chain_id, + "--key-file", + &key_file.to_string_lossy(), + "--key-name", + relayer, + ]; + let mut hermes = run_hermes_cmd(&hermes_dir, args, Some(20))?; + hermes.assert_success(); + } else { + let key_file_path = chain_dir + .join(format!("{}_seed.json", constants::COSMOS_RELAYER)); + let args = [ + "keys", + "add", + "--chain", + chain_id, + "--key-file", + &key_file_path.to_string_lossy(), + ]; + let mut hermes = run_hermes_cmd(&hermes_dir, args, Some(20))?; + hermes.assert_success(); + } + }, + Err(_) => { + let key_file_path = wallet::wallet_file(&chain_dir); + let args = [ + "keys", + "add", + "--chain", + chain_id, + "--key-file", + &key_file_path.to_string_lossy(), + ]; + let mut hermes = run_hermes_cmd(&hermes_dir, args, Some(20))?; + hermes.assert_success(); + }, + }; - let args = [ - "keys", - "add", - "--chain", - chain_id, - "--key-file", - &key_file_path.to_string_lossy(), - ]; - let mut hermes = run_hermes_cmd(test, args, Some(20))?; - hermes.assert_success(); + } - Ok(()) + Ok(hermes_dir) } pub fn run_hermes_cmd( - test: &Test, + hermes_dir: &TestDir, args: I, timeout_sec: Option, ) -> Result @@ -1277,7 +1375,8 @@ where S: AsRef, { let mut run_cmd = Command::new("hermes"); - let hermes_dir = test.test_dir.as_ref().join("hermes"); + let hermes_dir: &Path = hermes_dir.as_ref(); + let hermes_dir = hermes_dir.join("hermes"); run_cmd.current_dir(hermes_dir.clone()); let config_path = hermes_dir.join("config.toml"); run_cmd.args(["--config", &config_path.to_string_lossy()]); @@ -1300,7 +1399,7 @@ where let log_path = { let mut rng = rand::thread_rng(); - let log_dir = test.get_base_dir(Who::NonValidator).join("logs"); + let log_dir = hermes_dir.join("logs"); std::fs::create_dir_all(&log_dir)?; log_dir.join(format!( "{}-hermes-{}.log", @@ -1332,37 +1431,112 @@ where #[derive(Clone, Copy, Debug)] pub enum CosmosChainType { - Gaia, + Gaia(Option), CosmWasm, + Osmosis, } impl CosmosChainType { - pub fn chain_id(&self) -> &str { + fn genesis_cmd_args<'a>(&self, mut args: Vec<&'a str>) -> Vec<&'a str> { + if !matches!(self, CosmosChainType::Osmosis) { + args.insert(0, "genesis"); + } + args + } + + fn add_genesis_account_args<'a>( + &self, + account: &'a str, + coins: &'a str, + ) -> Vec<&'a str> { + self.genesis_cmd_args(vec!["add-genesis-account", account, coins]) + } + + fn gentx_args<'a>( + &self, + account: &'a str, + coins: &'a str, + chain_id: &'a str, + ) -> Vec<&'a str> { + self.genesis_cmd_args(vec![ + "gentx", + account, + coins, + "--keyring-backend", + "test", + "--chain-id", + chain_id, + ]) + } + + fn collect_gentxs_args<'a>(&self) -> Vec<&'a str> { + self.genesis_cmd_args(vec!["collect-gentxs"]) + } + + pub fn chain_id(&self) -> String { match self { - Self::Gaia => constants::GAIA_CHAIN_ID, - Self::CosmWasm => constants::COSMWASM_CHAIN_ID, + Self::Gaia(Some(suffix)) => { + format!("{}{}", constants::GAIA_CHAIN_ID, suffix) + } + Self::Gaia(_) => constants::GAIA_CHAIN_ID.to_string(), + Self::CosmWasm => constants::COSMWASM_CHAIN_ID.to_string(), + Self::Osmosis => constants::OSMOSIS_CHAIN_ID.to_string(), } } pub fn command_path(&self) -> &str { match self { - Self::Gaia => "gaiad", + Self::Gaia(_) => "gaiad", Self::CosmWasm => "wasmd", + Self::Osmosis => "osmosisd", } } pub fn chain_type(chain_id: &str) -> Result { - match chain_id { - constants::GAIA_CHAIN_ID => Ok(Self::Gaia), - constants::COSMWASM_CHAIN_ID => Ok(Self::CosmWasm), - _ => Err(eyre!(format!("Unexpected Cosmos chain ID: {chain_id}"))), + if chain_id == constants::COSMWASM_CHAIN_ID { + return Ok(Self::CosmWasm); + } + if chain_id == constants::OSMOSIS_CHAIN_ID { + return Ok(Self::Osmosis); + } + match chain_id.strip_prefix(constants::GAIA_CHAIN_ID) { + Some("") => Ok(Self::Gaia(None)), + Some(suffix) => { + Ok(Self::Gaia(Some(suffix.parse().map_err(|_| { + eyre!("Unexpected Cosmos chain ID: {chain_id}") + })?))) + } + _ => Err(eyre!("Unexpected Cosmos chain ID: {chain_id}")), } } pub fn account_prefix(&self) -> &str { match self { - Self::Gaia => "cosmos", + Self::Gaia(_) => "cosmos", Self::CosmWasm => "wasm", + Self::Osmosis => "osmo", + } + } + + pub fn get_p2p_port_number(&self) -> u64 { + 10_000 + self.get_offset() + } + + pub fn get_rpc_port_number(&self) -> u64 { + 20_000 + self.get_offset() + } + + pub fn get_grpc_port_number(&self) -> u64 { + 30_000 + self.get_offset() + } + + fn get_offset(&self) -> u64 { + // NB: ensure none of these ever conflict + match self { + Self::CosmWasm => 0, + Self::Osmosis => 1, + Self::Gaia(None) => 2, + Self::Gaia(Some(off)) => 3 + *off, } } } @@ -1371,7 +1545,7 @@ pub fn setup_cosmos(chain_type: CosmosChainType) -> Result { let working_dir = working_dir(); let test_dir = TestDir::new(); let chain_id = chain_type.chain_id(); - let cosmos_dir = test_dir.as_ref().join(chain_id); + let cosmos_dir = test_dir.as_ref().join(&chain_id); let net = Network { chain_id: ChainId(chain_id.to_string()), }; @@ -1383,7 +1557,7 @@ pub fn setup_cosmos(chain_type: CosmosChainType) -> Result { }; // initialize - let args = ["--chain-id", chain_id, "init", chain_id]; + let args = ["--chain-id", &chain_id, "init", &chain_id]; let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); @@ -1413,47 +1587,34 @@ pub fn setup_cosmos(chain_type: CosmosChainType) -> Result { // Add tokens to a user account let account = find_cosmos_address(&test, constants::COSMOS_USER)?; - let args = [ - "genesis", - "add-genesis-account", - &account, - "100000000stake,1000samoleans", - ]; + let args = chain_type + .add_genesis_account_args(&account, "100000000stake,1000samoleans"); let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); // Add the stake token to the relayer let account = find_cosmos_address(&test, constants::COSMOS_RELAYER)?; - let args = ["genesis", "add-genesis-account", &account, "10000stake"]; + let args = chain_type.add_genesis_account_args(&account, "1000000stake"); let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); // Add the stake token to the validator let validator = find_cosmos_address(&test, constants::COSMOS_VALIDATOR)?; - let args = [ - "genesis", - "add-genesis-account", - &validator, - "200000000000stake", - ]; + let args = + chain_type.add_genesis_account_args(&validator, "200000000000stake"); let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); // stake - let args = [ - "genesis", - "gentx", + let args = chain_type.gentx_args( constants::COSMOS_VALIDATOR, "100000000000stake", - "--keyring-backend", - "test", - "--chain-id", - chain_id, - ]; + &chain_id, + ); let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); - let args = ["genesis", "collect-gentxs"]; + let args = chain_type.collect_gentxs_args(); let mut cosmos = run_cosmos_cmd(&test, args, Some(10))?; cosmos.assert_success(); @@ -1462,6 +1623,67 @@ pub fn setup_cosmos(chain_type: CosmosChainType) -> Result { Ok(test) } +pub fn run_cosmos_cmd_homeless( + test: &Test, + args: I, + timeout_sec: Option, +) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let chain_id = test.net.chain_id.as_str(); + let chain_type = CosmosChainType::chain_type(chain_id)?; + let mut run_cmd = Command::new(chain_type.command_path()); + run_cmd.args(args); + + let args: String = + run_cmd.get_args().map(|s| s.to_string_lossy()).join(" "); + let cmd_str = + format!("{} {}", run_cmd.get_program().to_string_lossy(), args); + + let session = Session::spawn(run_cmd).map_err(|e| { + eyre!( + "\n\n{}: {}\n{}: {}", + "Failed to run Cosmos command".underline().red(), + cmd_str, + "Error".underline().red(), + e + ) + })?; + + let log_path = { + let mut rng = rand::thread_rng(); + let log_dir = test.get_base_dir(Who::NonValidator).join("logs"); + std::fs::create_dir_all(&log_dir)?; + log_dir.join(format!( + "{}-cosmos-{}.log", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_micros(), + rng.gen::() + )) + }; + let logger = OpenOptions::new() + .write(true) + .create_new(true) + .open(&log_path)?; + let mut session = expectrl::session::log(session, logger).unwrap(); + + session.set_expect_timeout(timeout_sec.map(std::time::Duration::from_secs)); + + let cmd_process = NamadaCmd { + session, + cmd_str, + log_path, + }; + + println!("{}:\n{}", "> Running".underline().green(), &cmd_process); + + Ok(cmd_process) +} + pub fn run_cosmos_cmd( test: &Test, args: I, @@ -1577,10 +1799,10 @@ pub mod constants { pub const APFEL: &str = "Apfel"; pub const KARTOFFEL: &str = "Kartoffel"; - // Gaia or CosmWasm + // Gaia or CosmWasm or Osmosis pub const GAIA_CHAIN_ID: &str = "gaia"; + pub const OSMOSIS_CHAIN_ID: &str = "osmosis"; pub const COSMWASM_CHAIN_ID: &str = "cosmwasm"; - pub const COSMOS_RPC: &str = "127.0.0.1:26657"; pub const COSMOS_USER: &str = "user"; pub const COSMOS_RELAYER: &str = "relayer"; pub const COSMOS_VALIDATOR: &str = "validator"; diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 283dc0f723..25e2b27267 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use namada_core::address::Address; use namada_core::token::Amount; +use namada_ibc::context::middlewares::create_transfer_middlewares; pub use namada_ibc::event::{IbcEvent, IbcEventType}; pub use namada_ibc::storage::{ burn_tokens, client_state_key, is_ibc_key, mint_limit_key, mint_tokens, @@ -19,7 +20,7 @@ pub use namada_ibc::{ }; use namada_tx_env::TxEnv; -use crate::{token, Ctx, Result}; +use crate::{parameters, token, Ctx, Result}; /// IBC actions to handle an IBC message. The `verifiers` inserted into the set /// must be inserted into the tx context with `Ctx::insert_verifier` after tx @@ -30,7 +31,10 @@ pub fn ibc_actions( let ctx = Rc::new(RefCell::new(ctx.clone())); let verifiers = Rc::new(RefCell::new(BTreeSet::
::new())); let mut actions = IbcActions::new(ctx.clone(), verifiers.clone()); - let module = TransferModule::new(ctx.clone(), verifiers); + let module = create_transfer_middlewares::<_, parameters::Store>( + ctx.clone(), + verifiers, + ); actions.add_transfer_module(module); let module = NftTransferModule::>::new(ctx); actions.add_transfer_module(module); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 343b1db0ad..1155881536 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1362,6 +1362,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "dur" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5b6c91b5e394b75cd96c36393fc938496c030220207a0ccf34d6cd313d3b49" +dependencies = [ + "nom", + "rust_decimal", +] + [[package]] name = "duration-str" version = "0.10.0" @@ -2550,7 +2560,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-apps", "ibc-clients", @@ -2563,7 +2573,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer-types", "ibc-core", @@ -2573,7 +2583,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "base64 0.22.1", "borsh", @@ -2594,7 +2604,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-transfer-types", "ibc-core", @@ -2604,7 +2614,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2622,7 +2632,7 @@ dependencies = [ [[package]] name = "ibc-apps" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer", "ibc-app-transfer", @@ -2631,7 +2641,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "ibc-client-tendermint-types", @@ -2648,7 +2658,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "displaydoc", "ibc-core-client-types", @@ -2665,7 +2675,7 @@ dependencies = [ [[package]] name = "ibc-client-wasm-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "base64 0.22.1", "displaydoc", @@ -2679,7 +2689,7 @@ dependencies = [ [[package]] name = "ibc-clients" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-tendermint", "ibc-client-wasm-types", @@ -2688,7 +2698,7 @@ dependencies = [ [[package]] name = "ibc-core" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -2704,7 +2714,7 @@ dependencies = [ [[package]] name = "ibc-core-channel" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel-types", "ibc-core-client", @@ -2719,7 +2729,7 @@ dependencies = [ [[package]] name = "ibc-core-channel-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2742,7 +2752,7 @@ dependencies = [ [[package]] name = "ibc-core-client" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-client-context", "ibc-core-client-types", @@ -2755,7 +2765,7 @@ dependencies = [ [[package]] name = "ibc-core-client-context" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -2771,7 +2781,7 @@ dependencies = [ [[package]] name = "ibc-core-client-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2791,7 +2801,7 @@ dependencies = [ [[package]] name = "ibc-core-commitment-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2810,7 +2820,7 @@ dependencies = [ [[package]] name = "ibc-core-connection" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-wasm-types", "ibc-core-client", @@ -2824,7 +2834,7 @@ dependencies = [ [[package]] name = "ibc-core-connection-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2845,7 +2855,7 @@ dependencies = [ [[package]] name = "ibc-core-handler" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -2860,7 +2870,7 @@ dependencies = [ [[package]] name = "ibc-core-handler-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2884,7 +2894,7 @@ dependencies = [ [[package]] name = "ibc-core-host" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -2902,7 +2912,7 @@ dependencies = [ [[package]] name = "ibc-core-host-cosmos" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -2925,7 +2935,7 @@ dependencies = [ [[package]] name = "ibc-core-host-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2940,7 +2950,7 @@ dependencies = [ [[package]] name = "ibc-core-router" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -2954,7 +2964,7 @@ dependencies = [ [[package]] name = "ibc-core-router-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -2973,17 +2983,118 @@ dependencies = [ [[package]] name = "ibc-derive" version = "0.8.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "proc-macro2", "quote", "syn 2.0.52", ] +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-overflow-receive" +version = "0.4.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-app-transfer-types", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "ibc-middleware-packet-forward" +version = "0.9.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "borsh", + "dur", + "either", + "ibc-app-transfer-types", + "ibc-core-channel", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-primitives", + "serde", + "serde_json", +] + [[package]] name = "ibc-primitives" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -3023,7 +3134,7 @@ dependencies = [ [[package]] name = "ibc-query" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "displaydoc", "ibc", @@ -3034,7 +3145,7 @@ dependencies = [ [[package]] name = "ibc-testkit" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "basecoin-store", "derive_more", @@ -3565,6 +3676,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -3764,8 +3881,13 @@ version = "0.46.1" dependencies = [ "borsh", "data-encoding", + "dur", "ibc", "ibc-derive", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0)", + "ibc-middleware-overflow-receive", + "ibc-middleware-packet-forward", "ibc-testkit", "ics23", "konst", @@ -3876,6 +3998,7 @@ name = "namada_sdk" version = "0.46.1" dependencies = [ "async-trait", + "bech32 0.8.1", "bimap", "borsh", "circular-queue", @@ -4049,7 +4172,9 @@ version = "0.46.1" dependencies = [ "concat-idents", "derivative", + "dur", "hyper 0.14.27", + "ibc-middleware-packet-forward", "ibc-testkit", "ics23", "itertools 0.12.1", @@ -4319,6 +4444,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -4931,9 +5066,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -5072,9 +5207,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -5466,12 +5601,18 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" dependencies = [ "arrayvec", + "borsh", + "bytes", "num-traits 0.2.17", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", ] [[package]] @@ -5834,11 +5975,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] diff --git a/wasm_for_tests/Cargo.lock b/wasm_for_tests/Cargo.lock index f578ba42bc..7eddbbf19d 100644 --- a/wasm_for_tests/Cargo.lock +++ b/wasm_for_tests/Cargo.lock @@ -23,6 +23,17 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -381,6 +392,28 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -683,6 +716,16 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "dur" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5b6c91b5e394b75cd96c36393fc938496c030220207a0ccf34d6cd313d3b49" +dependencies = [ + "nom", + "rust_decimal", +] + [[package]] name = "dyn-clone" version = "1.0.16" @@ -1027,6 +1070,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.14.3" @@ -1100,7 +1152,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-apps", "ibc-clients", @@ -1113,7 +1165,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer-types", "ibc-core", @@ -1123,7 +1175,7 @@ dependencies = [ [[package]] name = "ibc-app-nft-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "base64 0.22.1", "borsh", @@ -1144,7 +1196,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-transfer-types", "ibc-core", @@ -1154,7 +1206,7 @@ dependencies = [ [[package]] name = "ibc-app-transfer-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1172,7 +1224,7 @@ dependencies = [ [[package]] name = "ibc-apps" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-app-nft-transfer", "ibc-app-transfer", @@ -1181,7 +1233,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "ibc-client-tendermint-types", @@ -1198,7 +1250,7 @@ dependencies = [ [[package]] name = "ibc-client-tendermint-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "displaydoc", "ibc-core-client-types", @@ -1215,7 +1267,7 @@ dependencies = [ [[package]] name = "ibc-client-wasm-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "base64 0.22.1", "displaydoc", @@ -1229,7 +1281,7 @@ dependencies = [ [[package]] name = "ibc-clients" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-tendermint", "ibc-client-wasm-types", @@ -1238,7 +1290,7 @@ dependencies = [ [[package]] name = "ibc-core" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -1254,7 +1306,7 @@ dependencies = [ [[package]] name = "ibc-core-channel" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel-types", "ibc-core-client", @@ -1269,7 +1321,7 @@ dependencies = [ [[package]] name = "ibc-core-channel-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1292,7 +1344,7 @@ dependencies = [ [[package]] name = "ibc-core-client" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-client-context", "ibc-core-client-types", @@ -1305,7 +1357,7 @@ dependencies = [ [[package]] name = "ibc-core-client-context" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -1321,7 +1373,7 @@ dependencies = [ [[package]] name = "ibc-core-client-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1341,7 +1393,7 @@ dependencies = [ [[package]] name = "ibc-core-commitment-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1360,7 +1412,7 @@ dependencies = [ [[package]] name = "ibc-core-connection" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-client-wasm-types", "ibc-core-client", @@ -1374,7 +1426,7 @@ dependencies = [ [[package]] name = "ibc-core-connection-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1395,7 +1447,7 @@ dependencies = [ [[package]] name = "ibc-core-handler" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "ibc-core-channel", "ibc-core-client", @@ -1410,7 +1462,7 @@ dependencies = [ [[package]] name = "ibc-core-handler-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1434,7 +1486,7 @@ dependencies = [ [[package]] name = "ibc-core-host" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -1452,7 +1504,7 @@ dependencies = [ [[package]] name = "ibc-core-host-cosmos" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -1475,7 +1527,7 @@ dependencies = [ [[package]] name = "ibc-core-host-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1490,7 +1542,7 @@ dependencies = [ [[package]] name = "ibc-core-router" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "derive_more", "displaydoc", @@ -1504,7 +1556,7 @@ dependencies = [ [[package]] name = "ibc-core-router-types" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1523,17 +1575,118 @@ dependencies = [ [[package]] name = "ibc-derive" version = "0.8.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "proc-macro2", "quote", "syn 2.0.65", ] +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-primitives", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-module-macros" +version = "0.1.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "ibc-middleware-overflow-receive" +version = "0.4.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0#8d341de14ff5e2a637699796cffbf0fbbaee001f" +dependencies = [ + "ibc-app-transfer-types", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=orm/v0.4.0)", + "ibc-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "ibc-middleware-packet-forward" +version = "0.9.0" +source = "git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0#3d3b436f7c58000c7498d68e88c15a955433a619" +dependencies = [ + "borsh", + "dur", + "either", + "ibc-app-transfer-types", + "ibc-core-channel", + "ibc-core-channel-types", + "ibc-core-host-types", + "ibc-core-router", + "ibc-core-router-types", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=pfm/v0.9.0)", + "ibc-primitives", + "serde", + "serde_json", +] + [[package]] name = "ibc-primitives" version = "0.54.0" -source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38bd2a32f35117d4d9165a3c68c64ccd87ad56dd#38bd2a32f35117d4d9165a3c68c64ccd87ad56dd" +source = "git+https://github.com/heliaxdev/cosmos-ibc-rs?rev=38489943c4e75206eaffeeeec6153c039c2499d1#38489943c4e75206eaffeeeec6153c039c2499d1" dependencies = [ "borsh", "derive_more", @@ -1659,7 +1812,7 @@ source = "git+https://github.com/heliaxdev/indexmap?tag=2.2.4-heliax-1#b5b5b547b dependencies = [ "borsh", "equivalent", - "hashbrown", + "hashbrown 0.14.3", "serde", ] @@ -1670,7 +1823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] @@ -1927,6 +2080,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "multimap" version = "0.8.3" @@ -2049,8 +2208,13 @@ version = "0.46.1" dependencies = [ "borsh", "data-encoding", + "dur", "ibc", "ibc-derive", + "ibc-middleware-module 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module/v0.1.0)", + "ibc-middleware-module-macros 0.1.0 (git+https://github.com/heliaxdev/ibc-middleware?tag=module-macros/v0.1.0)", + "ibc-middleware-overflow-receive", + "ibc-middleware-packet-forward", "ics23", "konst", "masp_primitives", @@ -2386,6 +2550,16 @@ dependencies = [ "sha2 0.9.9", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nonempty" version = "0.7.0" @@ -2767,9 +2941,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2860,11 +3034,31 @@ dependencies = [ "prost 0.13.2", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -3005,6 +3199,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -3024,6 +3227,35 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "rlp" version = "0.5.2" @@ -3046,6 +3278,22 @@ dependencies = [ "svgbobdoc", ] +[[package]] +name = "rust_decimal" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits 0.2.17", + "rand", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-hex" version = "2.1.0" @@ -3134,6 +3382,12 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.7.3" @@ -3218,11 +3472,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -3298,6 +3553,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.9" @@ -3898,6 +4159,12 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + [[package]] name = "version_check" version = "0.9.4"