diff --git a/Cargo.toml b/Cargo.toml index 8ee82d52..19f8128c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ - "contracts/cosmwasm-vm/*", - "contracts/soroban/contracts/*", - "contracts/soroban/libs/*" + "contracts/cosmwasm-vm/*" ] [workspace.package] @@ -38,8 +36,6 @@ cw-common={ git="https://github.com/icon-project/IBC-Integration.git", branch = cw-mock-dapp = {path="contracts/cosmwasm-vm/cw-mock-dapp"} cw-mock-dapp-multi = { path="contracts/cosmwasm-vm/cw-mock-dapp-multi"} -soroban-sdk = "21.6.0" - [profile.release] opt-level = 'z' debug = false diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index e571a9ff..9ac56a3a 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -130,6 +130,8 @@ name = "centralized-connection" version = "0.0.0" dependencies = [ "soroban-sdk", + "soroban-xcall-lib", + "xcall", ] [[package]] @@ -338,7 +340,6 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", - "spki", ] [[package]] @@ -353,15 +354,16 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", + "subtle", "zeroize", ] @@ -383,7 +385,6 @@ dependencies = [ "ff", "generic-array", "group", - "pkcs8", "rand_core", "sec1", "subtle", @@ -597,9 +598,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", - "signature", ] [[package]] @@ -716,6 +715,18 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -760,6 +771,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -848,7 +868,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -959,9 +978,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "soroban-builtin-sdk-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32c6e817f3ca269764ec0d7d14da6210b74a5bf14d4e745aa3ee860558900" +checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" dependencies = [ "itertools", "proc-macro2", @@ -971,9 +990,9 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14e18d879c520ff82612eaae0590acaf6a7f3b977407e1abb1c9e31f94c7814" +checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" dependencies = [ "arbitrary", "crate-git-revision", @@ -985,13 +1004,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-xdr", + "wasmparser", ] [[package]] name = "soroban-env-guest" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5122ca2abd5ebcc1e876a96b9b44f87ce0a0e06df8f7c09772ddb58b159b7454" +checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" dependencies = [ "soroban-env-common", "static_assertions", @@ -999,13 +1019,16 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" +checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" dependencies = [ "backtrace", "curve25519-dalek", + "ecdsa", "ed25519-dalek", + "elliptic-curve", + "generic-array", "getrandom", "hex-literal", "hmac", @@ -1013,8 +1036,10 @@ dependencies = [ "num-derive", "num-integer", "num-traits", + "p256", "rand", "rand_chacha", + "sec1", "sha2", "sha3", "soroban-builtin-sdk-macros", @@ -1022,13 +1047,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-strkey", + "wasmparser", ] [[package]] name = "soroban-env-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13e3f8c86f812e0669e78fcb3eae40c385c6a9dd1a4886a1de733230b4fcf27" +checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" dependencies = [ "itertools", "proc-macro2", @@ -1041,9 +1067,9 @@ dependencies = [ [[package]] name = "soroban-ledger-snapshot" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a54708f44890e0546180db6b4f530e2a88d83b05a9b38a131caa21d005e25a" +checksum = "956476365ff3f9bf429ff23fa11ac75798347a2bfc3c9e5e12638dbe3a6b17a8" dependencies = [ "serde", "serde_json", @@ -1062,9 +1088,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fc8be9068dd4e0212d8b13ad61089ea87e69ac212c262914503a961c8dc3a3" +checksum = "c7767472f00a4053e86d5c37b3c814a6bc01c9230004713328d73d2a3444e72e" dependencies = [ "arbitrary", "bytes-lit", @@ -1082,9 +1108,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db20def4ead836663633f58d817d0ed8e1af052c9650a04adf730525af85b964" +checksum = "be8cf8fa10f3ad62509ff7b25cd696fb837da692c40264d1abb393e117aad75c" dependencies = [ "crate-git-revision", "darling", @@ -1102,9 +1128,9 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eefeb5d373b43f6828145d00f0c5cc35e96db56a6671ae9614f84beb2711cab" +checksum = "12d306f61ef5c1247dca1562e04cc74b6e3adf107631c168b2ce0d5f1cf1fa13" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1114,9 +1140,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3152bca4737ef734ac37fe47b225ee58765c9095970c481a18516a2b287c7a33" +checksum = "bed06e0f622fb878fc439643f2fd86163223ac33a468beeea96e5d33f79b08b3" dependencies = [ "prettyplease", "proc-macro2", @@ -1183,9 +1209,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "20.1.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59cdf3eb4467fb5a4b00b52e7de6dca72f67fac6f9b700f55c95a5d86f09c9d" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1369,11 +1395,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.88.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.2.6", + "semver", ] [[package]] diff --git a/contracts/soroban/Cargo.toml b/contracts/soroban/Cargo.toml index cddad916..7cb79f87 100644 --- a/contracts/soroban/Cargo.toml +++ b/contracts/soroban/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.dependencies] -soroban-sdk = "20.5.0" +soroban-sdk = "21.7.4" [profile.release] opt-level = "z" diff --git a/contracts/soroban/contracts/centralized-connection/Cargo.toml b/contracts/soroban/contracts/centralized-connection/Cargo.toml index c4f294fa..e34ece52 100644 --- a/contracts/soroban/contracts/centralized-connection/Cargo.toml +++ b/contracts/soroban/contracts/centralized-connection/Cargo.toml @@ -10,6 +10,8 @@ doctest = false [dependencies] soroban-sdk = { workspace = true, features = ["alloc"] } +xcall = { path = "../xcall" } +soroban-xcall-lib = { path = "../../libs/soroban-xcall-lib" } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/soroban/contracts/centralized-connection/src/contract.rs b/contracts/soroban/contracts/centralized-connection/src/contract.rs index 507f5451..03444560 100644 --- a/contracts/soroban/contracts/centralized-connection/src/contract.rs +++ b/contracts/soroban/contracts/centralized-connection/src/contract.rs @@ -125,11 +125,13 @@ impl CentralizedConnection { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/centralized-connection/src/storage.rs b/contracts/soroban/contracts/centralized-connection/src/storage.rs index 4a410aa0..a4a2fa24 100644 --- a/contracts/soroban/contracts/centralized-connection/src/storage.rs +++ b/contracts/soroban/contracts/centralized-connection/src/storage.rs @@ -50,13 +50,6 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } -pub fn get_conn_sn(e: &Env) -> Result { - e.storage() - .instance() - .get(&StorageKey::ConnSn) - .ok_or(ContractError::Uninitialized) -} - pub fn get_next_conn_sn(e: &Env) -> u128 { let mut sn = e.storage().instance().get(&StorageKey::ConnSn).unwrap_or(0); sn += 1; @@ -103,6 +96,19 @@ pub fn get_sn_receipt(e: &Env, network_id: String, sn: u128) -> bool { is_received } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_receipt(e: &Env, network_id: String, sn: u128) { let key = StorageKey::Receipts(network_id, sn); e.storage().persistent().set(&key, &true); diff --git a/contracts/soroban/contracts/centralized-connection/src/test.rs b/contracts/soroban/contracts/centralized-connection/src/test.rs index a44bd217..7556c384 100644 --- a/contracts/soroban/contracts/centralized-connection/src/test.rs +++ b/contracts/soroban/contracts/centralized-connection/src/test.rs @@ -2,9 +2,14 @@ extern crate std; -mod xcall { +mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +mod connection { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" + ); +} use crate::{ contract::{CentralizedConnection, CentralizedConnectionClient}, @@ -13,9 +18,14 @@ use crate::{ types::InitializeMsg, }; use soroban_sdk::{ - symbol_short, + bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, - token, vec, Address, Bytes, Env, IntoVal, String, Symbol, + token, vec, Address, Bytes, Env, IntoVal, String, Symbol, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; +use xcall::{ + storage as xcall_storage, + types::{message::CSMessage, request::CSMessageRequest, rollback::Rollback}, }; pub struct TestContext { @@ -33,11 +43,12 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { - xcall: env.register_contract_wasm(None, xcall::WASM), + xcall: env.register_contract_wasm(None, xcall_module::WASM), contract: env.register_contract(None, CentralizedConnection), relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "icon"), upgrade_authority: Address::generate(&env), env, @@ -54,6 +65,23 @@ impl TestContext { xcall_address: self.xcall.clone(), upgrade_authority: self.upgrade_authority.clone(), }); + + self.init_xcall_state(); + } + + pub fn init_xcall_state(&self) { + let xcall_client = xcall_module::Client::new(&self.env, &self.xcall); + + let initialize_msg = xcall_module::InitializeMsg { + native_token: self.native_token.clone(), + network_id: self.nid.clone(), + sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), + }; + xcall_client.initialize(&initialize_msg); + + xcall_client.set_protocol_fee(&100_u128); + xcall_client.set_default_connection(&self.nid, &self.contract) } pub fn init_send_message(&self, client: &CentralizedConnectionClient<'static>) { @@ -65,9 +93,11 @@ impl TestContext { } fn get_dummy_initialize_msg(env: &Env) -> InitializeMsg { + let native_token_contract = env.register_stellar_asset_contract_v2(Address::generate(&env)); + InitializeMsg { relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(Address::generate(&env)), + native_token: native_token_contract.address(), xcall_address: Address::generate(&env), upgrade_authority: Address::generate(&env), } @@ -343,3 +373,108 @@ fn test_get_receipt_returns_false() { let receipt = client.get_receipt(&ctx.nid, &sequence_no); assert_eq!(receipt, true) } + +#[test] +fn test_recv_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #5)")] +fn test_recv_message_duplicate_connection_sequence() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); + + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +pub fn test_revert_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let to = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let rollback = Rollback::new( + Address::generate(&ctx.env), + to, + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.xcall, || { + xcall_storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.revert_message(&sequence_no); + + ctx.env.as_contract(&ctx.xcall, || { + // rollback should be enabled + let rollback = xcall_storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); +} + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(connection::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/centralized-connection/src/types.rs b/contracts/soroban/contracts/centralized-connection/src/types.rs index e3f08bdb..6b8c2fab 100644 --- a/contracts/soroban/contracts/centralized-connection/src/types.rs +++ b/contracts/soroban/contracts/centralized-connection/src/types.rs @@ -8,6 +8,7 @@ pub enum StorageKey { UpgradeAuthority, Xlm, ConnSn, + Version, NetworkFee(String), Receipts(String, u128), } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs index dc3c8573..cd47ec6a 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs @@ -125,9 +125,16 @@ impl MockDapp { helpers::ensure_admin(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) + } + fn process_message( message_type: u8, data: Bytes, diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs index 2a866ea2..e7f2533f 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs @@ -26,6 +26,19 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, admin: Address) { e.storage().instance().set(&StorageKey::Admin, &admin); } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs index 09977786..037f1876 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs @@ -213,3 +213,16 @@ fn test_handle_call_message_reply() { &Some(vec![&ctx.env]), ); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = MockDappClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(mock_dapp::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs index ccaa3877..17e173ec 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs @@ -3,16 +3,22 @@ use soroban_xcall_lib::network_address::NetworkAddress; use crate::contract::{MockDapp, MockDappClient}; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } -mod xcall_module { +pub mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +pub mod mock_dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + pub fn get_dummy_network_address(env: &Env) -> NetworkAddress { let network_id = String::from_str(&env, "stellar"); let account = String::from_str( @@ -31,6 +37,7 @@ pub struct TestContext { pub env: Env, pub native_token: Address, pub xcall: Address, + pub upgrade_authority: Address, pub centralized_connection: Address, } @@ -38,14 +45,16 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let address = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(address); Self { contract: env.register_contract(None, MockDapp), nid: String::from_str(&env, "stellar"), admin: Address::generate(&env), - native_token: env.register_stellar_asset_contract(address), + native_token: native_token_contract.address(), network_address: get_dummy_network_address(&env), xcall: env.register_contract_wasm(None, xcall_module::WASM), + upgrade_authority: Address::generate(&env), centralized_connection: env.register_contract_wasm(None, connection::WASM), env, } @@ -72,6 +81,7 @@ impl TestContext { native_token: self.native_token.clone(), network_id: self.nid.clone(), sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), }; xcall_client.initialize(&initialize_msg); @@ -86,6 +96,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: Address::generate(&self.env), xcall_address: self.xcall.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs index 88ab759e..8b23c376 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs @@ -6,6 +6,7 @@ pub enum StorageKey { Admin, Xlm, Sn, + Version, Rollback(u128), Connections(String), } diff --git a/contracts/soroban/contracts/xcall/src/contract.rs b/contracts/soroban/contracts/xcall/src/contract.rs index 47c2eec9..8d81f6ac 100644 --- a/contracts/soroban/contracts/xcall/src/contract.rs +++ b/contracts/soroban/contracts/xcall/src/contract.rs @@ -153,11 +153,13 @@ impl Xcall { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/xcall/src/errors.rs b/contracts/soroban/contracts/xcall/src/errors.rs index e9896817..ad05c5c3 100644 --- a/contracts/soroban/contracts/xcall/src/errors.rs +++ b/contracts/soroban/contracts/xcall/src/errors.rs @@ -21,4 +21,6 @@ pub enum ContractError { InvalidReplyReceived = 15, InvalidRlpLength = 16, NoRollbackData = 17, + NetworkIdMismatch = 18, + InvalidSourceNetwork = 19, } diff --git a/contracts/soroban/contracts/xcall/src/handle_message.rs b/contracts/soroban/contracts/xcall/src/handle_message.rs index 56831a99..e76bcfed 100644 --- a/contracts/soroban/contracts/xcall/src/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/handle_message.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Bytes, Env, String, Vec}; +use soroban_sdk::{Address, Bytes, BytesN, Env, String, Vec}; use crate::{ errors::ContractError, @@ -21,7 +21,7 @@ pub fn handle_message( let config = storage::get_config(&env)?; if config.network_id == from_nid { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::InvalidSourceNetwork); } let cs_message: CSMessage = CSMessage::decode(&env, msg)?; @@ -43,7 +43,7 @@ pub fn handle_request( let (src_net, _) = req.from().parse_network_address(&env); if src_net != from_net { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::NetworkIdMismatch); } let source = sender.to_string(); let source_valid = is_valid_source(&env, &source, src_net, &req.protocols())?; @@ -52,7 +52,7 @@ pub fn handle_request( } if req.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_request = storage::get_pending_request(&env, hash.clone()); if !pending_request.contains(source.clone()) { @@ -96,7 +96,7 @@ pub fn handle_result(env: &Env, sender: &Address, data: Bytes) -> Result<(), Con } if rollback.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_response = storage::get_pending_response(&env, hash.clone()); if !pending_response.contains(source.clone()) { @@ -179,8 +179,10 @@ pub fn is_valid_source( return Ok(true); } if protocols.len() == 0 { - let default_connection = storage::default_connection(e, src_net)?; - return Ok(sender.clone() == default_connection.to_string()); + let default_connection = storage::default_connection(e, src_net); + if default_connection.is_ok() { + return Ok(sender.clone() == default_connection.unwrap().to_string()); + } } Ok(false) } diff --git a/contracts/soroban/contracts/xcall/src/helpers.rs b/contracts/soroban/contracts/xcall/src/helpers.rs index 5c6ff75d..6bd74db4 100644 --- a/contracts/soroban/contracts/xcall/src/helpers.rs +++ b/contracts/soroban/contracts/xcall/src/helpers.rs @@ -24,13 +24,6 @@ pub fn ensure_upgrade_authority(e: &Env) -> Result { Ok(authority) } -pub fn ensure_fee_handler(e: &Env) -> Result { - let fee_handler = storage::get_fee_handler(&e)?; - fee_handler.require_auth(); - - Ok(fee_handler) -} - pub fn ensure_data_size(len: usize) -> Result<(), ContractError> { if len > MAX_DATA_SIZE as usize { return Err(ContractError::MaxDataSizeExceeded); diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs index 5deef0c5..cfa43a98 100644 --- a/contracts/soroban/contracts/xcall/src/storage.rs +++ b/contracts/soroban/contracts/xcall/src/storage.rs @@ -153,6 +153,19 @@ pub fn get_own_network_address(e: &Env) -> Result Ok(from) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, address: &Address) { e.storage().instance().set(&StorageKey::Admin, &address); extend_instance(&e); diff --git a/contracts/soroban/contracts/xcall/src/test/contract.rs b/contracts/soroban/contracts/xcall/src/test/contract.rs index e7a4d06f..5ad3267a 100644 --- a/contracts/soroban/contracts/xcall/src/test/contract.rs +++ b/contracts/soroban/contracts/xcall/src/test/contract.rs @@ -156,6 +156,20 @@ fn test_get_fee() { assert_eq!(fee, protocol_fee + centralized_conn_fee) } +#[test] +fn test_get_network_address() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let network_address = client.get_network_address(); + let expected_network_address = String::from_str( + &ctx.env, + "icon/CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + ); + assert_eq!(network_address, expected_network_address); +} + #[test] fn test_set_upgrade_authority() { let ctx = TestContext::default(); @@ -183,3 +197,16 @@ fn test_set_upgrade_authority() { let autorhity = client.get_upgrade_authority(); assert_eq!(autorhity, new_upgrade_authority); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(xcall::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/xcall/src/test/execute_call.rs b/contracts/soroban/contracts/xcall/src/test/execute_call.rs index 108ef360..69ca24c7 100644 --- a/contracts/soroban/contracts/xcall/src/test/execute_call.rs +++ b/contracts/soroban/contracts/xcall/src/test/execute_call.rs @@ -1,10 +1,188 @@ #![cfg(test)] -use crate::{contract::XcallClient, storage, types::rollback::Rollback}; -use soroban_sdk::{bytes, testutils::Address as _, Address}; +use soroban_rlp::encoder; +use soroban_sdk::{ + bytes, + testutils::{Address as _, Events}, + vec, Address, Bytes, IntoVal, String, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; + +use crate::{ + contract::XcallClient, + event::{CallExecutedEvent, RollbackExecutedEvent}, + storage, + types::{request::CSMessageRequest, rollback::Rollback}, +}; use super::setup::*; +#[test] +fn test_execute_call_with_persistent_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let sequence_no = 1; + let from = NetworkAddress::new(&ctx.env, ctx.nid, ctx.contract.to_string()); + let mut req = CSMessageRequest::new( + from, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessagePersisted, + Bytes::new(&ctx.env), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_call_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "rollback")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 0, + msg: String::from_str(&ctx.env, "unknown error"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_rollback_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "abc")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + Vec::new(&ctx.env), + MessageType::CallMessageWithRollback, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().get(1).unwrap()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #11)")] +fn test_execute_call_data_mismatch() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let req = get_dummy_message_request(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_execute_rollback_fail_for_invalid_sequence_number() { @@ -36,3 +214,44 @@ fn test_execute_rollback_fail_not_enabled() { client.execute_rollback(&sequence_no); } + +#[test] +fn test_execute_rollback_success() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = Rollback::new( + ctx.dapp, + ctx.network_address, + get_dummy_sources(&ctx.env), + Bytes::new(&ctx.env), + true, + ); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.execute_rollback(&sequence_no); + + let rollback_executed_event = RollbackExecutedEvent { sn: sequence_no }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("RollbackExecuted",).into_val(&ctx.env), + rollback_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be removed + assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); + }); +} diff --git a/contracts/soroban/contracts/xcall/src/test/handle_message.rs b/contracts/soroban/contracts/xcall/src/test/handle_message.rs index 286213a1..5788e7d9 100644 --- a/contracts/soroban/contracts/xcall/src/test/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/handle_message.rs @@ -1,11 +1,13 @@ #![cfg(test)] +extern crate std; + use soroban_sdk::{ bytes, - testutils::{Address as _, Events}, - vec, Address, IntoVal, String, + testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, + vec, Address, Bytes, BytesN, IntoVal, String, Symbol, }; -use soroban_xcall_lib::messages::msg_type::MessageType; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; use crate::{ contract::XcallClient, @@ -15,13 +17,14 @@ use crate::{ message::CSMessage, request::CSMessageRequest, result::{CSMessageResult, CSResponseType}, + rollback::Rollback, }, }; use super::setup::*; #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #19)")] fn test_handle_message_fail_for_same_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -35,7 +38,7 @@ fn test_handle_message_fail_for_same_network_id() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #18)")] fn test_handle_message_request_fail_for_invalid_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -82,6 +85,35 @@ fn test_handle_message_request_fail_for_invalid_source() { ); } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #9)")] +fn test_handle_message_request_fail_for_invalid_source_2() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + vec![&ctx.env], + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request).encode(&ctx.env); + + client.handle_message( + &Address::generate(&ctx.env), + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_request_from_default_connection() { let ctx = TestContext::default(); @@ -142,7 +174,7 @@ fn test_handle_message_request_from_multiple_sources() { assert_eq!(res, ()); let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); - let hash = ctx.env.crypto().keccak256(cs_message.payload()); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); ctx.env.as_contract(&ctx.contract, || { let pending_requests = storage::get_pending_request(&ctx.env, hash); @@ -177,6 +209,56 @@ fn test_handle_message_request_from_multiple_sources() { ) } +#[test] +fn test_handle_message_result_from_multiple_protocols() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols = get_dummy_protocols(&ctx.env); + + let sequence_no = 1; + let rollback = Rollback::new( + Address::generate(&ctx.env), + get_dummy_network_address(&ctx.env), + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + Bytes::new(&ctx.env), + ); + let encoded = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + for (i, protocol) in protocols.iter().enumerate() { + let from_nid = String::from_str(&ctx.env, "s"); + let sender = Address::from_string(&protocol); + + let res = client.handle_message(&sender, &from_nid, &encoded); + assert_eq!(res, ()); + + let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); + + ctx.env.as_contract(&ctx.contract, || { + let pending_responses = storage::get_pending_response(&ctx.env, hash); + + let i = i as u32 + 1; + if i < protocols.len() { + assert_eq!(pending_responses.len(), i) + } else { + assert_eq!(pending_responses.len(), 0) + } + }) + } +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_handle_message_result_fail_for_invalid_sequence_no() { @@ -284,6 +366,46 @@ fn test_handle_message_result_should_enable_rollback_when_response_is_failure_fr ) } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #15)")] +fn test_handle_message_result_when_invalid_reply_received() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + request.encode(&ctx.env), + ); + let cs_message = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + client.handle_message( + &ctx.centralized_connection, + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_result_when_response_is_success_from_dst_chain() { let ctx = TestContext::default(); @@ -318,6 +440,26 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { &cs_message, ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_message"), + ( + &ctx.centralized_connection, + String::from_str(&ctx.env, "cosmos"), + cs_message + ) + .into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + ctx.env.as_contract(&ctx.contract, || { // rollback should be removed assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); @@ -366,5 +508,66 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { call_msg_event.into_val(&ctx.env) ) ] - ) + ); +} + +#[test] +fn test_handle_error() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.handle_error(&ctx.centralized_connection, &sequence_no); + + let response_msg_event = ResponseMsgEvent { + sn: sequence_no, + code: 0_u32, + }; + let rollback_msg_event = RollbackMsgEvent { sn: sequence_no }; + + let mut events = ctx.env.events().all(); + events.pop_front(); + + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("ResponseMessage",).into_val(&ctx.env), + response_msg_event.into_val(&ctx.env) + ), + ( + client.address.clone(), + ("RollbackMessage",).into_val(&ctx.env), + rollback_msg_event.into_val(&ctx.env) + ) + ] + ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_error"), + (&ctx.centralized_connection, sequence_no,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be enabled + let rollback = storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); } diff --git a/contracts/soroban/contracts/xcall/src/test/send_message.rs b/contracts/soroban/contracts/xcall/src/test/send_message.rs index c46af375..f605046d 100644 --- a/contracts/soroban/contracts/xcall/src/test/send_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/send_message.rs @@ -5,11 +5,11 @@ extern crate std; use soroban_sdk::{ bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, - vec, Address, Bytes, IntoVal, String, + vec, Address, Bytes, IntoVal, String, Vec, }; use soroban_xcall_lib::messages::{ - call_message::CallMessage, call_message_rollback::CallMessageWithRollback, envelope::Envelope, - AnyMessage, + call_message::CallMessage, call_message_persisted::CallMessagePersisted, + call_message_rollback::CallMessageWithRollback, envelope::Envelope, AnyMessage, }; use super::setup::*; @@ -211,6 +211,30 @@ fn test_process_rollback_message_with_empty_rollback_data() { .unwrap(); } +#[test] +fn test_process_persisted_message() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg = CallMessagePersisted { + data: bytes!(&ctx.env, 0xab), + }; + let message = AnyMessage::CallMessagePersisted(msg); + let envelope = &get_dummy_envelope_msg(&ctx.env, message); + + ctx.env.as_contract(&client.address, || { + let res = send_message::process_message( + &ctx.env, + &ctx.network_address, + 1, + &ctx.contract, + envelope, + ); + assert!(res.is_ok()) + }); +} + #[test] fn test_process_rollback_message() { let ctx = TestContext::default(); @@ -304,6 +328,32 @@ fn test_call_connection_for_call_message() { assert_eq!(connection_balance, fee); } +#[test] +fn test_call_connection_with_empty_sources() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + ctx.env.mock_all_auths_allowing_non_root_auth(); + + let sender = Address::generate(&ctx.env); + let need_response = false; + let fee = ctx.get_centralized_connection_fee(need_response); + ctx.mint_native_token(&sender, fee); + + ctx.env.as_contract(&ctx.contract, || { + send_message::call_connection( + &ctx.env, + &sender, + &ctx.nid, + 1, + Vec::new(&ctx.env), + need_response, + Bytes::new(&ctx.env), + ) + .unwrap(); + }) +} + #[test] fn test_calim_protocol_fee() { let ctx = TestContext::default(); diff --git a/contracts/soroban/contracts/xcall/src/test/setup.rs b/contracts/soroban/contracts/xcall/src/test/setup.rs index e4591430..8aa7d49e 100644 --- a/contracts/soroban/contracts/xcall/src/test/setup.rs +++ b/contracts/soroban/contracts/xcall/src/test/setup.rs @@ -10,11 +10,20 @@ use soroban_xcall_lib::{ network_address::NetworkAddress, }; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } +pub mod dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + +pub mod xcall { + soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); +} use crate::{ contract::{Xcall, XcallClient}, @@ -115,6 +124,7 @@ pub struct TestContext { pub token_admin: Address, pub network_address: NetworkAddress, pub upgrade_authority: Address, + pub dapp: Address, pub centralized_connection: Address, } @@ -122,18 +132,21 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let dapp = env.register_contract_wasm(None, dapp::WASM); let centralized_connection = env.register_contract_wasm(None, connection::WASM); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { contract: env.register_contract(None, Xcall), admin: Address::generate(&env), fee_handler: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "stellar"), network_address: get_dummy_network_address(&env), upgrade_authority: Address::generate(&env), env, token_admin, + dapp, centralized_connection, } } @@ -151,6 +164,8 @@ impl TestContext { self.init_connection_state(); client.set_protocol_fee(&100); client.set_default_connection(&self.nid, &self.centralized_connection); + + self.init_dapp_state(); } pub fn init_connection_state(&self) { @@ -160,6 +175,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: self.admin.clone(), xcall_address: self.contract.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); @@ -168,6 +184,17 @@ impl TestContext { connection_client.set_fee(&self.nid, &message_fee, &response_fee); } + pub fn init_dapp_state(&self) { + let dapp_client = dapp::Client::new(&self.env, &self.dapp); + dapp_client.init(&self.admin, &self.contract.clone(), &self.native_token); + + dapp_client.add_connection( + &self.centralized_connection.to_string(), + &Address::generate(&self.env).to_string(), + &self.nid, + ); + } + pub fn mint_native_token(&self, address: &Address, amount: u128) { let native_token_client = token::StellarAssetClient::new(&self.env, &self.native_token); native_token_client.mint(&address, &(*&amount as i128)); diff --git a/contracts/soroban/contracts/xcall/src/types/request.rs b/contracts/soroban/contracts/xcall/src/types/request.rs index cb91857e..4decd4a0 100644 --- a/contracts/soroban/contracts/xcall/src/types/request.rs +++ b/contracts/soroban/contracts/xcall/src/types/request.rs @@ -61,11 +61,6 @@ impl CSMessageRequest { msg_type == MessageType::CallMessageWithRollback } - pub fn allow_retry(&self) -> bool { - let msg_type: MessageType = (self.msg_type as u8).into(); - msg_type == MessageType::CallMessagePersisted - } - pub fn data(&self) -> &Bytes { &self.data } diff --git a/contracts/soroban/contracts/xcall/src/types/storage_types.rs b/contracts/soroban/contracts/xcall/src/types/storage_types.rs index f24d82e7..fec97168 100644 --- a/contracts/soroban/contracts/xcall/src/types/storage_types.rs +++ b/contracts/soroban/contracts/xcall/src/types/storage_types.rs @@ -15,6 +15,7 @@ pub enum StorageKey { PendingResponses(BytesN<32>), LastReqId, UpgradeAuthority, + Version, } #[contracttype]