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]