diff --git a/.github/workflows/stellar-build-and-test.yml b/.github/workflows/stellar-build-and-test.yml
index bd23e8620..9c2cffe0e 100644
--- a/.github/workflows/stellar-build-and-test.yml
+++ b/.github/workflows/stellar-build-and-test.yml
@@ -27,7 +27,7 @@ jobs:
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
- toolchain: 1.79.0
+ toolchain: 1.81.0
target: wasm32-unknown-unknown
override: true
profile: minimal
diff --git a/Cargo.lock b/Cargo.lock
index 3c98cba39..224d4770e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -197,6 +197,14 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "cluster-connection"
+version = "0.0.0"
+dependencies = [
+ "soroban-rlp",
+ "soroban-sdk",
+]
+
[[package]]
name = "common"
version = "0.1.0"
diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java
index 0568de377..16d600cf7 100644
--- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java
+++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java
@@ -2,6 +2,7 @@
import java.math.BigInteger;
+import score.ByteArrayObjectWriter;
import score.Context;
import score.ObjectReader;
import score.ObjectWriter;
@@ -66,8 +67,6 @@ public Packet(String srcNetwork, String srcContractAddress, BigInteger srcSn, Bi
|| srcHeight == null || dstNetwork == null || dstContractAddress == null || data == null;
Context.require(!isIllegalArg,
"srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, and data cannot be null");
- if (isIllegalArg) {
- }
this.srcNetwork = srcNetwork;
this.srcContractAddress = srcContractAddress;
this.srcSn = srcSn;
@@ -77,12 +76,8 @@ public Packet(String srcNetwork, String srcContractAddress, BigInteger srcSn, Bi
this.data = data;
}
- public String getId() {
- return createId(this.srcNetwork, this.srcContractAddress, this.srcSn);
- }
-
- public static String createId(String srcNetwork, String contractAddress, BigInteger srcSn) {
- return srcNetwork + "/" + contractAddress + "/" + srcSn.toString();
+ public byte[] getId() {
+ return Context.hash("sha-256", this.toBytes());
}
/**
@@ -173,4 +168,10 @@ public static Packet readObject(ObjectReader r) {
r.end();
return p;
}
+
+ public byte[] toBytes() {
+ ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn");
+ Packet.writeObject(writer, this);
+ return writer.toByteArray();
+ }
}
diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java
index 3ee6315f2..c597f81a1 100644
--- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java
+++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java
@@ -41,17 +41,16 @@ public class RelayAggregator {
private final ArrayDB
relayers = Context.newArrayDB("relayers", Address.class);
private final DictDB relayersLookup = Context.newDictDB("relayersLookup", Boolean.class);
- private final DictDB packets = Context.newDictDB("packets", Packet.class);
- private final DictDB acknowledgedPackets = Context.newDictDB("acknowledgedPackets", Boolean.class);
+ private final DictDB acknowledgedPackets = Context.newDictDB("acknowledgedPackets", Boolean.class);
+ private final DictDB packets = Context.newDictDB("packets", Packet.class);
- private final BranchDB> signatures = Context.newBranchDB("signatures",
+ private final BranchDB> signatures = Context.newBranchDB("signatures",
byte[].class);
public RelayAggregator(Address _admin) {
if (admin.get() == null) {
admin.set(_admin);
signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD);
- addRelayer(_admin);
}
}
@@ -61,12 +60,6 @@ public void setAdmin(Address _admin) {
Context.require(admin.get() != _admin, "admin already set");
- // add new admin as relayer
- addRelayer(_admin);
-
- // remove old admin from relayer list
- removeRelayer(admin.get());
-
admin.set(_admin);
}
@@ -108,10 +101,9 @@ public void setRelayers(Address[] newRelayers, int threshold) {
addRelayer(newRelayer);
}
- Address adminAdrr = admin.get();
for (int i = 0; i < relayers.size(); i++) {
Address oldRelayer = relayers.get(i);
- if (!oldRelayer.equals(adminAdrr) && !newRelayersMap.containsKey(oldRelayer)) {
+ if (!newRelayersMap.containsKey(oldRelayer)) {
removeRelayer(oldRelayer);
}
}
@@ -128,8 +120,13 @@ public boolean packetSubmitted(
Address relayer,
String srcNetwork,
String srcContractAddress,
- BigInteger srcSn) {
- String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn);
+ BigInteger srcSn,
+ BigInteger srcHeight,
+ String dstNetwork,
+ String dstContractAddress,
+ byte[] data) {
+ Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
+ byte[] pktID = pkt.getId();
byte[] existingSign = signatures.at(pktID).get(relayer);
return existingSign != null;
}
@@ -138,8 +135,13 @@ public boolean packetSubmitted(
public boolean packetAcknowledged(
String srcNetwork,
String srcContractAddress,
- BigInteger srcSn) {
- String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn);
+ BigInteger srcSn,
+ BigInteger srcHeight,
+ String dstNetwork,
+ String dstContractAddress,
+ byte[] data) {
+ Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
+ byte[] pktID = pkt.getId();
return acknowledgedPackets.get(pktID) != null && acknowledgedPackets.get(pktID) == true;
}
@@ -157,7 +159,7 @@ public void submitPacket(
relayersOnly();
Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
- String pktID = pkt.getId();
+ byte[] pktID = pkt.getId();
if (acknowledgedPackets.get(pktID) != null && acknowledgedPackets.get(pktID) == true) {
return;
@@ -184,7 +186,7 @@ public void submitPacket(
setSignature(pktID, Context.getCaller(), signature);
if (signatureThresholdReached(pktID)) {
- byte[][] sigs = getSignatures(srcNetwork, srcContractAddress, srcSn);
+ byte[][] sigs = getSignatures(pktID);
byte[] encodedSigs = serializeSignatures(sigs);
PacketAcknowledged(
pkt.getSrcNetwork(),
@@ -200,8 +202,7 @@ public void submitPacket(
}
}
- private byte[][] getSignatures(String srcNetwork, String srcContractAddress, BigInteger srcSn) {
- String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn);
+ private byte[][] getSignatures(byte[] pktID) {
DictDB signDict = signatures.at(pktID);
ArrayList signatureList = new ArrayList();
@@ -220,7 +221,7 @@ private byte[][] getSignatures(String srcNetwork, String srcContractAddress, Big
return sigs;
}
- protected void setSignature(String pktID, Address addr, byte[] sign) {
+ protected void setSignature(byte[] pktID, Address addr, byte[] sign) {
signatures.at(pktID).set(addr, sign);
}
@@ -256,7 +257,7 @@ protected static byte[][] deserializeSignatures(byte[] encodedSigs) {
}
private void adminOnly() {
- Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the leader relayer");
+ Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the admin");
}
private void relayersOnly() {
@@ -285,7 +286,7 @@ private void removeRelayer(Address oldRelayer) {
}
}
- private Boolean signatureThresholdReached(String pktID) {
+ private Boolean signatureThresholdReached(byte[] pktID) {
int noOfSignatures = 0;
for (int i = 0; i < relayers.size(); i++) {
Address relayer = relayers.get(i);
@@ -297,7 +298,7 @@ private Boolean signatureThresholdReached(String pktID) {
return noOfSignatures >= signatureThreshold.get();
}
- private void removePacket(String pktID) {
+ private void removePacket(byte[] pktID) {
packets.set(pktID, null);
DictDB signDict = signatures.at(pktID);
diff --git a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java
index bb946d982..9f1137d17 100644
--- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java
+++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java
@@ -1,7 +1,6 @@
package relay.aggregator;
import java.math.BigInteger;
-import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -21,9 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -67,8 +64,7 @@ void setup() throws Exception {
aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress());
- Address[] relayers = new Address[] { adminAc.getAddress(), relayerOneAc.getAddress(),
- relayerTwoAc.getAddress(),
+ Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress(),
relayerThreeAc.getAddress() };
aggregator.invoke(adminAc, "setRelayers", (Object) relayers, 2);
@@ -79,21 +75,11 @@ void setup() throws Exception {
@Test
public void testSetAdmin() {
- Address oldAdmin = (Address) aggregator.call("getAdmin");
-
Account newAdminAc = sm.createAccount();
aggregator.invoke(adminAc, "setAdmin", newAdminAc.getAddress());
Address newAdmin = (Address) aggregator.call("getAdmin");
assertEquals(newAdminAc.getAddress(), newAdmin);
-
- Address[] relayers = (Address[]) aggregator.call("getRelayers");
-
- boolean containsNewAdmin = Arrays.asList(relayers).contains(newAdmin);
- boolean containsOldAdmin = Arrays.asList(relayers).contains(oldAdmin);
-
- assertTrue(containsNewAdmin);
- assertFalse(containsOldAdmin);
}
@Test
@@ -104,7 +90,7 @@ public void testSetAdmin_unauthorized() {
Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress());
UserRevertedException e = assertThrows(UserRevertedException.class, action);
- assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage());
+ assertEquals("Reverted(0): Unauthorized: caller is not the admin", e.getMessage());
}
@Test
@@ -124,7 +110,7 @@ public void testSetSignatureThreshold_unauthorised() {
"setSignatureThreshold", threshold);
UserRevertedException e = assertThrows(UserRevertedException.class, action);
- assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer",
+ assertEquals("Reverted(0): Unauthorized: caller is not the admin",
e.getMessage());
}
@@ -139,7 +125,7 @@ public void testSetRelayers() {
Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers");
- Address[] expectedRelayers = new Address[] { adminAc.getAddress(), relayerThreeAc.getAddress(),
+ Address[] expectedRelayers = new Address[] { relayerThreeAc.getAddress(),
relayerFourAc.getAddress(),
relayerFiveAc.getAddress() };
@@ -170,7 +156,7 @@ public void testSetRelayers_unauthorized() {
(Object) newRelayers, threshold);
UserRevertedException e = assertThrows(UserRevertedException.class, action);
- assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer",
+ assertEquals("Reverted(0): Unauthorized: caller is not the admin",
e.getMessage());
}
@@ -229,19 +215,23 @@ public void testPacketSubmitted_true() throws Exception {
boolean submitted = (boolean) aggregator.call("packetSubmitted",
relayerOneAc.getAddress(), srcNetwork,
- srcContractAddress, srcSn);
+ srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
assertEquals(submitted, true);
}
@Test
public void testPacketSubmitted_false() throws Exception {
String srcNetwork = "0x2.icon";
+ String dstNetwork = "sui";
BigInteger srcSn = BigInteger.ONE;
+ BigInteger srcHeight = BigInteger.ONE;
String srcContractAddress = "hxjuiod";
+ String dstContractAddress = "hxjuiod";
+ byte[] data = new byte[] { 0x01, 0x02 };
boolean submitted = (boolean) aggregator.call("packetSubmitted",
relayerOneAc.getAddress(), srcNetwork,
- srcContractAddress, srcSn);
+ srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
assertEquals(submitted, false);
}
@@ -259,12 +249,6 @@ public void testPacketAcknowledged_true() throws Exception {
byte[] dataHash = Context.hash("sha-256", data);
- byte[] signAdmin = admin.sign(dataHash);
- aggregator.invoke(adminAc, "submitPacket", srcNetwork, srcContractAddress,
- srcSn, srcHeight, dstNetwork,
- dstContractAddress, data,
- signAdmin);
-
byte[] signOne = relayerOne.sign(dataHash);
aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork,
srcContractAddress, srcSn, srcHeight, dstNetwork,
@@ -272,15 +256,22 @@ public void testPacketAcknowledged_true() throws Exception {
data,
signOne);
+ byte[] signTwo = relayerTwo.sign(dataHash);
+ aggregator.invoke(relayerTwoAc, "submitPacket", srcNetwork,
+ srcContractAddress, srcSn, srcHeight, dstNetwork,
+ dstContractAddress,
+ data,
+ signTwo);
+
byte[][] sigs = new byte[2][];
- sigs[0] = signAdmin;
- sigs[1] = signOne;
+ sigs[0] = signOne;
+ sigs[1] = signTwo;
byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs);
byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs);
- assertArrayEquals(signAdmin, decodedSigs[0]);
- assertArrayEquals(signOne, decodedSigs[1]);
+ assertArrayEquals(signOne, decodedSigs[0]);
+ assertArrayEquals(signTwo, decodedSigs[1]);
verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress,
srcSn, srcHeight, dstNetwork,
@@ -289,49 +280,10 @@ public void testPacketAcknowledged_true() throws Exception {
boolean acknowledged = (boolean) aggregator.call("packetAcknowledged",
srcNetwork,
- srcContractAddress, srcSn);
+ srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data);
assertEquals(acknowledged, true);
}
- @Test
- public void testPacketAcknowledged_false() throws Exception {
- String srcNetwork = "0x2.icon";
- BigInteger srcSn = BigInteger.ONE;
- String srcContractAddress = "hxjuiod";
-
- boolean acknowledged = (boolean) aggregator.call("packetAcknowledged",
- srcNetwork,
- srcContractAddress, srcSn);
- assertEquals(acknowledged, false);
- }
-
- @Test
- public void testSubmitPacket() throws Exception {
- String srcNetwork = "0x2.icon";
- String dstNetwork = "sui";
- BigInteger srcSn = BigInteger.ONE;
- BigInteger srcHeight = BigInteger.ONE;
- String srcContractAddress = "hxjuiod";
- String dstContractAddress = "hxjuiod";
- byte[] data = new byte[] { 0x01, 0x02 };
-
- aggregator.invoke(adminAc, "setSignatureThreshold", 2);
-
- byte[] dataHash = Context.hash("sha-256", data);
- byte[] sign = relayerOne.sign(dataHash);
-
- aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork,
- srcContractAddress, srcSn, srcHeight, dstNetwork,
- dstContractAddress, data,
- sign);
-
- String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn);
- verify(aggregatorSpy).PacketRegistered(srcNetwork, srcContractAddress, srcSn,
- srcHeight, dstNetwork,
- dstContractAddress, data);
- verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign);
- }
-
@Test
public void testSubmitPacket_thresholdReached() throws Exception {
String srcNetwork = "0x2.icon";
@@ -346,12 +298,6 @@ public void testSubmitPacket_thresholdReached() throws Exception {
byte[] dataHash = Context.hash("sha-256", data);
- byte[] signAdmin = admin.sign(dataHash);
- aggregator.invoke(adminAc, "submitPacket", srcNetwork, srcContractAddress,
- srcSn, srcHeight, dstNetwork,
- dstContractAddress, data,
- signAdmin);
-
byte[] signOne = relayerOne.sign(dataHash);
aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork,
srcContractAddress, srcSn, srcHeight, dstNetwork,
@@ -359,15 +305,22 @@ public void testSubmitPacket_thresholdReached() throws Exception {
data,
signOne);
+ byte[] signTwo = relayerTwo.sign(dataHash);
+ aggregator.invoke(relayerTwoAc, "submitPacket", srcNetwork,
+ srcContractAddress, srcSn, srcHeight, dstNetwork,
+ dstContractAddress,
+ data,
+ signTwo);
+
byte[][] sigs = new byte[2][];
- sigs[0] = signAdmin;
- sigs[1] = signOne;
+ sigs[0] = signOne;
+ sigs[1] = signTwo;
byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs);
byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs);
- assertArrayEquals(signAdmin, decodedSigs[0]);
- assertArrayEquals(signOne, decodedSigs[1]);
+ assertArrayEquals(signOne, decodedSigs[0]);
+ assertArrayEquals(signTwo, decodedSigs[1]);
verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress,
srcSn, srcHeight, dstNetwork,
diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock
index 9ac56a3ad..fbf3411ee 100644
--- a/contracts/soroban/Cargo.lock
+++ b/contracts/soroban/Cargo.lock
@@ -153,6 +153,14 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "cluster-connection"
+version = "0.0.0"
+dependencies = [
+ "soroban-rlp",
+ "soroban-sdk",
+]
+
[[package]]
name = "const-oid"
version = "0.9.6"
diff --git a/contracts/soroban/contracts/cluster-connection/Cargo.toml b/contracts/soroban/contracts/cluster-connection/Cargo.toml
new file mode 100644
index 000000000..e6f6b4820
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "cluster-connection"
+version = "0.0.0"
+edition = "2021"
+publish = false
+
+[lib]
+crate-type = ["cdylib"]
+doctest = false
+
+[dependencies]
+soroban-sdk = { workspace = true, features = ["alloc"] }
+soroban-rlp = { path = "../../libs/soroban-rlp" }
+
+[dev-dependencies]
+soroban-sdk = { workspace = true, features = ["testutils"] }
diff --git a/contracts/soroban/contracts/cluster-connection/src/contract.rs b/contracts/soroban/contracts/cluster-connection/src/contract.rs
new file mode 100644
index 000000000..b02d54eec
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs
@@ -0,0 +1,185 @@
+use soroban_sdk::{contract, contractimpl, token, Address, Bytes, BytesN, Env, String, Vec};
+
+use crate::{errors::ContractError, event, helpers, storage, types::InitializeMsg};
+
+#[contract]
+pub struct ClusterConnection;
+
+#[contractimpl]
+impl ClusterConnection {
+ pub fn initialize(env: Env, msg: InitializeMsg) -> Result<(), ContractError> {
+ storage::is_initialized(&env)?;
+
+ storage::store_native_token(&env, msg.native_token);
+ storage::store_conn_sn(&env, 0);
+ storage::store_relayer(&env, msg.relayer);
+ storage::store_admin(&env, msg.admin);
+ storage::store_xcall(&env, msg.xcall_address);
+ storage::store_upgrade_authority(&env, msg.upgrade_authority);
+ storage::store_validator_threshold(&env, 0);
+ storage::store_validators(&env, Vec::new(&env));
+
+ Ok(())
+ }
+
+ pub fn get_admin(env: Env) -> Result {
+ let address = storage::admin(&env)?;
+ Ok(address)
+ }
+
+ pub fn set_admin(env: Env, address: Address) -> Result<(), ContractError> {
+ helpers::ensure_admin(&env)?;
+ storage::store_admin(&env, address);
+ Ok(())
+ }
+
+ pub fn get_upgrade_authority(env: Env) -> Result {
+ let address = storage::get_upgrade_authority(&env)?;
+ Ok(address)
+ }
+
+ pub fn set_upgrade_authority(env: &Env, address: Address) -> Result<(), ContractError> {
+ helpers::ensure_upgrade_authority(&env)?;
+ storage::store_upgrade_authority(&env, address);
+
+ Ok(())
+ }
+
+ pub fn set_relayer(env: Env, address: Address) -> Result<(), ContractError> {
+ helpers::ensure_admin(&env)?;
+ storage::store_relayer(&env, address);
+ Ok(())
+ }
+
+ pub fn send_message(
+ env: Env,
+ tx_origin: Address,
+ to: String,
+ sn: i64,
+ msg: Bytes,
+ ) -> Result<(), ContractError> {
+ helpers::ensure_xcall(&env)?;
+
+ let next_conn_sn = storage::get_next_conn_sn(&env);
+ storage::store_conn_sn(&env, next_conn_sn);
+
+ let mut fee: u128 = 0;
+ if sn >= 0 {
+ fee = helpers::get_network_fee(&env, to.clone(), sn > 0)?;
+ }
+ if fee > 0 {
+ helpers::transfer_token(&env, &tx_origin, &env.current_contract_address(), &fee)?;
+ }
+ event::send_message(&env, to, next_conn_sn, msg);
+
+ Ok(())
+ }
+
+ pub fn recv_message_with_signatures(
+ env: Env,
+ src_network: String,
+ conn_sn: u128,
+ msg: Bytes,
+ signatures: Vec>,
+ ) -> Result<(), ContractError> {
+ helpers::ensure_relayer(&env)?;
+
+ if !helpers::verify_signatures(&env, signatures, &src_network, &conn_sn, &msg){
+ return Err(ContractError::SignatureVerificationFailed);
+ };
+
+ if storage::get_sn_receipt(&env, src_network.clone(), conn_sn) {
+ return Err(ContractError::DuplicateMessage);
+ }
+ storage::store_receipt(&env, src_network.clone(), conn_sn);
+
+ helpers::call_xcall_handle_message(&env, &src_network, msg)?;
+ Ok(())
+ }
+
+ pub fn set_fee(
+ env: Env,
+ network_id: String,
+ message_fee: u128,
+ response_fee: u128,
+ ) -> Result<(), ContractError> {
+ helpers::ensure_relayer(&env)?;
+
+ storage::store_network_fee(&env, network_id, message_fee, response_fee);
+ Ok(())
+ }
+
+ pub fn claim_fees(env: Env) -> Result<(), ContractError> {
+ let admin = helpers::ensure_relayer(&env)?;
+
+ let token_addr = storage::native_token(&env)?;
+ let client = token::Client::new(&env, &token_addr);
+ let balance = client.balance(&env.current_contract_address());
+
+ client.transfer(&env.current_contract_address(), &admin, &balance);
+ Ok(())
+ }
+
+ pub fn update_validators(env: Env, pub_keys: Vec>, threshold: u32) -> Result<(), ContractError> {
+ helpers::ensure_admin(&env)?;
+ let mut validators = Vec::new(&env);
+
+ for address in pub_keys.clone() {
+ if !validators.contains(&address) {
+ validators.push_back(address);
+ }
+ }
+ if (validators.len() as u32) < threshold {
+ return Err(ContractError::ThresholdExceeded);
+
+ }
+ storage::store_validators(&env, pub_keys);
+ storage::store_validator_threshold(&env, threshold);
+ Ok(())
+ }
+
+ pub fn get_validators_threshold(env: Env) -> Result {
+ let threshold = storage::get_validators_threshold(&env).unwrap();
+ Ok(threshold)
+ }
+
+ pub fn set_validators_threshold(env: Env, threshold: u32) -> Result<(), ContractError> {
+ helpers::ensure_admin(&env)?;
+ let validators = storage::get_validators(&env).unwrap();
+ if (validators.len() as u32) < threshold {
+ return Err(ContractError::ThresholdExceeded);
+ }
+ storage::store_validator_threshold(&env, threshold);
+ Ok(())
+ }
+
+ pub fn get_validators(env: Env) -> Result>, ContractError> {
+ let validators = storage::get_validators(&env).unwrap();
+ Ok(validators)
+ }
+
+ pub fn get_relayer(env: Env) -> Result {
+ let address = storage::relayer(&env)?;
+ Ok(address)
+ }
+
+ pub fn get_fee(env: Env, network_id: String, response: bool) -> Result {
+ helpers::get_network_fee(&env, network_id, response)
+ }
+
+ pub fn get_receipt(env: Env, network_id: String, sn: u128) -> bool {
+ storage::get_sn_receipt(&env, network_id, sn)
+ }
+
+ pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), ContractError> {
+ helpers::ensure_upgrade_authority(&env)?;
+ env.deployer().update_current_contract_wasm(new_wasm_hash);
+
+ Ok(())
+ }
+
+ pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> {
+ storage::extend_instance(&env);
+ Ok(())
+ }
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/errors.rs b/contracts/soroban/contracts/cluster-connection/src/errors.rs
new file mode 100644
index 000000000..b4d505179
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/errors.rs
@@ -0,0 +1,18 @@
+use soroban_sdk::contracterror;
+
+#[contracterror]
+#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
+#[repr(u32)]
+pub enum ContractError {
+ OnlyAdmin = 1,
+ Uninitialized = 2,
+ AlreadyInitialized = 3,
+ InsufficientFund = 4,
+ DuplicateMessage = 5,
+ NetworkNotSupported = 6,
+ CannotRemoveAdmin = 7,
+ ThresholdExceeded = 8,
+ ValidatorNotFound = 9,
+ ValidatorAlreadyAdded = 10,
+ SignatureVerificationFailed = 11,
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/event.rs b/contracts/soroban/contracts/cluster-connection/src/event.rs
new file mode 100644
index 000000000..6db6d215e
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/event.rs
@@ -0,0 +1,19 @@
+#![allow(non_snake_case)]
+
+use soroban_sdk::{contracttype, Bytes, Env, String};
+
+#[contracttype]
+pub struct SendMsgEvent {
+ pub targetNetwork: String,
+ pub connSn: u128,
+ pub msg: Bytes,
+}
+
+pub(crate) fn send_message(e: &Env, targetNetwork: String, connSn: u128, msg: Bytes) {
+ let emit_message = SendMsgEvent {
+ targetNetwork,
+ connSn,
+ msg,
+ };
+ e.events().publish(("Message",), emit_message);
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs
new file mode 100644
index 000000000..f37ed14fb
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs
@@ -0,0 +1,132 @@
+use soroban_sdk::{token, vec, Address, Bytes, BytesN, Env, Map, String, Vec};
+use crate::{errors::ContractError, interfaces::interface_xcall::XcallClient, storage};
+use soroban_rlp::encoder;
+
+pub fn ensure_relayer(e: &Env) -> Result {
+ let relayer = storage::relayer(&e)?;
+ relayer.require_auth();
+
+ Ok(relayer)
+}
+
+pub fn ensure_admin(e: &Env) -> Result {
+ let admin = storage::admin(&e)?;
+ admin.require_auth();
+
+ Ok(admin)
+}
+
+pub fn ensure_upgrade_authority(e: &Env) -> Result {
+ let authority = storage::get_upgrade_authority(&e)?;
+ authority.require_auth();
+
+ Ok(authority)
+}
+
+pub fn ensure_xcall(e: &Env) -> Result {
+ let xcall = storage::get_xcall(&e)?;
+ xcall.require_auth();
+
+ Ok(xcall)
+}
+
+pub fn get_network_fee(
+ env: &Env,
+ network_id: String,
+ response: bool,
+) -> Result {
+ let mut fee = storage::get_msg_fee(&env, network_id.clone())?;
+ if response {
+ fee += storage::get_res_fee(&env, network_id)?;
+ }
+
+ Ok(fee)
+}
+
+pub fn transfer_token(
+ e: &Env,
+ from: &Address,
+ to: &Address,
+ amount: &u128,
+) -> Result<(), ContractError> {
+ let native_token = storage::native_token(&e)?;
+ let client = token::Client::new(&e, &native_token);
+
+ client.transfer(&from, &to, &(*amount as i128));
+ Ok(())
+}
+
+pub fn verify_signatures(
+ e: &Env,
+ signatures: Vec>,
+ src_network: &String,
+ conn_sn: &u128,
+ message: &Bytes,
+) -> bool {
+ let validators = storage::get_validators(e).unwrap();
+ let threshold = storage::get_validators_threshold(e).unwrap();
+
+ if signatures.len() < threshold {
+ return false
+ }
+ let message_hash = e.crypto().keccak256(&get_encoded_message(e, src_network, conn_sn, message));
+ let mut unique_validators = Map::new(e);
+ let mut count = 0;
+
+
+ for sig in signatures.iter() {
+ let r_s_v = sig.to_array();
+ // Separate signature (r + s) and recovery ID
+ let signature_array: [u8; 64] = r_s_v[..64].try_into().unwrap(); // r + s part
+ let recovery_code = match r_s_v[64] {
+ rc if rc >= 27 => rc - 27,
+ rc => rc,
+ };
+ let signature = BytesN::<64>::from_array(e, &signature_array);
+
+ let public_key = e.crypto().secp256k1_recover(&message_hash, &signature, recovery_code as u32);
+
+ if validators.contains(&public_key) {
+ if !unique_validators.contains_key(public_key.clone()) {
+ unique_validators.set(public_key, count);
+ count += 1;
+ }
+ }
+ }
+ (unique_validators.len() as u32) >= threshold
+
+}
+
+
+pub fn get_encoded_message(e: &Env, src_network: &String, conn_sn: &u128, message: &Bytes) -> Bytes {
+ let mut list = vec![&e];
+ list.push_back(encoder::encode_string(&e, src_network.clone()));
+ list.push_back(encoder::encode_u128(&e, conn_sn.clone()));
+ list.push_back(encoder::encode(&e, message.clone()));
+
+ encoder::encode_list(&e, list, false)
+}
+
+#[cfg(not(test))]
+pub fn call_xcall_handle_message(e: &Env, nid: &String, msg: Bytes) -> Result<(), ContractError> {
+ let xcall_addr = storage::get_xcall(&e)?;
+ let client = XcallClient::new(&e, &xcall_addr);
+ client.handle_message(&e.current_contract_address(), nid, &msg);
+
+ Ok(())
+}
+
+#[cfg(test)]
+pub fn call_xcall_handle_message(_e: &Env, _nid: &String, _msg: Bytes) -> Result<(), ContractError> {
+ Ok(())
+}
+
+
+
+pub fn call_xcall_handle_error(e: &Env, sn: u128) -> Result<(), ContractError> {
+ let xcall_addr = storage::get_xcall(&e)?;
+ let client = XcallClient::new(&e, &xcall_addr);
+ client.handle_error(&e.current_contract_address(), &sn);
+
+ Ok(())
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/interfaces/interface_xcall.rs b/contracts/soroban/contracts/cluster-connection/src/interfaces/interface_xcall.rs
new file mode 100644
index 000000000..c49d6c9c7
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/interfaces/interface_xcall.rs
@@ -0,0 +1,15 @@
+use soroban_sdk::{contractclient, Address, Bytes, Env, String};
+
+use crate::errors::ContractError;
+
+#[contractclient(name = "XcallClient")]
+pub trait IXcall {
+ fn handle_message(
+ env: Env,
+ sender: Address,
+ from_nid: String,
+ msg: Bytes,
+ ) -> Result<(), ContractError>;
+
+ fn handle_error(env: Env, sender: Address, sequence_no: u128) -> Result<(), ContractError>;
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/interfaces/mod.rs b/contracts/soroban/contracts/cluster-connection/src/interfaces/mod.rs
new file mode 100644
index 000000000..61f8fdfe8
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/interfaces/mod.rs
@@ -0,0 +1 @@
+pub mod interface_xcall;
diff --git a/contracts/soroban/contracts/cluster-connection/src/lib.rs b/contracts/soroban/contracts/cluster-connection/src/lib.rs
new file mode 100644
index 000000000..1fd060736
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/lib.rs
@@ -0,0 +1,10 @@
+#![no_std]
+
+pub mod contract;
+pub mod errors;
+pub mod event;
+pub mod helpers;
+pub mod interfaces;
+pub mod storage;
+pub mod test;
+pub mod types;
diff --git a/contracts/soroban/contracts/cluster-connection/src/storage.rs b/contracts/soroban/contracts/cluster-connection/src/storage.rs
new file mode 100644
index 000000000..bdc017a16
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/storage.rs
@@ -0,0 +1,187 @@
+use soroban_sdk::{Address, BytesN, Env, String, Vec};
+
+use crate::{
+ errors::ContractError,
+ types::{NetworkFee, StorageKey},
+};
+
+const DAY_IN_LEDGERS: u32 = 17280; // assumes 5s a ledger
+
+const LEDGER_THRESHOLD_INSTANCE: u32 = DAY_IN_LEDGERS * 30; // ~ 30 days
+const LEDGER_BUMP_INSTANCE: u32 = LEDGER_THRESHOLD_INSTANCE + DAY_IN_LEDGERS; // ~ 31 days
+
+const LEDGER_THRESHOLD_PERSISTENT: u32 = DAY_IN_LEDGERS * 30; // ~ 30 days
+const LEDGER_BUMP_PERSISTENT: u32 = LEDGER_THRESHOLD_PERSISTENT + DAY_IN_LEDGERS; // ~ 31 days
+
+pub fn is_initialized(e: &Env) -> Result<(), ContractError> {
+ let initialized = e.storage().instance().has(&StorageKey::Admin);
+ if initialized {
+ Err(ContractError::AlreadyInitialized)
+ } else {
+ Ok(())
+ }
+}
+
+pub fn admin(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::Admin)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn relayer(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::Relayer)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn get_upgrade_authority(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::UpgradeAuthority)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn get_xcall(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::Xcall)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn native_token(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::Xlm)
+ .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;
+ sn
+}
+
+pub fn get_msg_fee(e: &Env, network_id: String) -> Result {
+ let key = StorageKey::NetworkFee(network_id);
+ let network_fee: NetworkFee = e
+ .storage()
+ .persistent()
+ .get(&key)
+ .unwrap_or(NetworkFee::default());
+
+ if network_fee.message_fee > 0 {
+ extend_persistent(e, &key);
+ }
+
+ Ok(network_fee.message_fee)
+}
+
+pub fn get_res_fee(e: &Env, network_id: String) -> Result {
+ let key = StorageKey::NetworkFee(network_id);
+ let network_fee: NetworkFee = e
+ .storage()
+ .persistent()
+ .get(&key)
+ .unwrap_or(NetworkFee::default());
+
+ if network_fee.response_fee > 0 {
+ extend_persistent(e, &key);
+ }
+
+ Ok(network_fee.response_fee)
+}
+
+pub fn get_sn_receipt(e: &Env, network_id: String, sn: u128) -> bool {
+ let key = StorageKey::Receipts(network_id, sn);
+ let is_received = e.storage().persistent().get(&key).unwrap_or(false);
+ if is_received {
+ extend_persistent(e, &key);
+ }
+
+ is_received
+}
+
+pub fn get_validators_threshold(e: &Env) -> Result {
+ e.storage()
+ .instance()
+ .get(&StorageKey::ValidatorThreshold)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn get_validators(e: &Env) -> Result>, ContractError> {
+ e.storage()
+ .instance()
+ .get(&StorageKey::Validators)
+ .ok_or(ContractError::Uninitialized)
+}
+
+pub fn store_receipt(e: &Env, network_id: String, sn: u128) {
+ let key = StorageKey::Receipts(network_id, sn);
+ e.storage().persistent().set(&key, &true);
+ extend_persistent(e, &key);
+}
+
+pub fn store_relayer(e: &Env, relayer: Address) {
+ e.storage().instance().set(&StorageKey::Relayer, &relayer);
+}
+
+pub fn store_admin(e: &Env, admin: Address) {
+ e.storage().instance().set(&StorageKey::Admin, &admin);
+}
+
+pub fn store_upgrade_authority(e: &Env, address: Address) {
+ e.storage()
+ .instance()
+ .set(&StorageKey::UpgradeAuthority, &address);
+}
+
+pub fn store_xcall(e: &Env, xcall: Address) {
+ e.storage().instance().set(&StorageKey::Xcall, &xcall);
+}
+
+pub fn store_native_token(e: &Env, address: Address) {
+ e.storage().instance().set(&StorageKey::Xlm, &address);
+}
+
+pub fn store_conn_sn(e: &Env, sn: u128) {
+ e.storage().instance().set(&StorageKey::ConnSn, &sn);
+}
+
+pub fn store_validator_threshold(e: &Env, threshold: u32) {
+ e.storage().instance().set(&StorageKey::ValidatorThreshold, &threshold);
+}
+
+pub fn store_validators(e: &Env, validators: Vec>) {
+ e.storage().instance().set(&StorageKey::Validators, &validators);
+}
+
+pub fn store_network_fee(e: &Env, network_id: String, message_fee: u128, response_fee: u128) {
+ let key = StorageKey::NetworkFee(network_id);
+ let network_fee = NetworkFee {
+ message_fee,
+ response_fee,
+ };
+ e.storage().persistent().set(&key, &network_fee);
+ extend_persistent(e, &key);
+}
+
+pub fn extend_instance(e: &Env) {
+ e.storage()
+ .instance()
+ .extend_ttl(LEDGER_THRESHOLD_INSTANCE, LEDGER_BUMP_INSTANCE);
+}
+
+pub fn extend_persistent(e: &Env, key: &StorageKey) {
+ e.storage()
+ .persistent()
+ .extend_ttl(key, LEDGER_THRESHOLD_PERSISTENT, LEDGER_BUMP_PERSISTENT);
+}
diff --git a/contracts/soroban/contracts/cluster-connection/src/test.rs b/contracts/soroban/contracts/cluster-connection/src/test.rs
new file mode 100644
index 000000000..fe375ba57
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/test.rs
@@ -0,0 +1,515 @@
+#![cfg(test)]
+
+extern crate std;
+
+mod xcall {
+ soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm");
+}
+
+use crate::{
+ contract::{ClusterConnection, ClusterConnectionClient},
+ event::SendMsgEvent,
+ storage,
+ types::InitializeMsg,
+};
+use soroban_sdk::{
+ symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, token, vec, Address, Bytes, BytesN, Env, IntoVal, String, Symbol, Vec
+};
+
+pub struct TestContext {
+ env: Env,
+ xcall: Address,
+ contract: Address,
+ admin:Address,
+ relayer: Address,
+ native_token: Address,
+ token_admin: Address,
+ nid: String,
+ upgrade_authority: Address,
+}
+
+impl TestContext {
+ pub fn default() -> Self {
+ let env = Env::default();
+ let token_admin = Address::generate(&env);
+ let xcall = env.register_contract_wasm(None, xcall::WASM);
+ Self {
+ xcall: xcall.clone(),
+ contract: env.register_contract(None, ClusterConnection),
+ relayer: Address::generate(&env),
+ admin: Address::generate(&env),
+ native_token: env.register_stellar_asset_contract(token_admin.clone()),
+ nid: String::from_str(&env, "0x2.icon"),
+ upgrade_authority: Address::generate(&env),
+ env,
+ token_admin,
+ }
+ }
+
+ pub fn init_context(&self, client: &ClusterConnectionClient<'static>) {
+ self.env.mock_all_auths();
+
+ client.initialize(&InitializeMsg {
+ admin: self.admin.clone(),
+ relayer: self.relayer.clone(),
+ native_token: self.native_token.clone(),
+ xcall_address: self.xcall.clone(),
+ upgrade_authority: self.upgrade_authority.clone(),
+ });
+
+ }
+
+ pub fn init_send_message(&self, client: &ClusterConnectionClient<'static>) {
+ self.init_context(&client);
+ self.env.mock_all_auths_allowing_non_root_auth();
+
+ client.set_fee(&self.nid, &100, &100);
+ }
+}
+
+fn get_dummy_initialize_msg(env: &Env) -> InitializeMsg {
+ InitializeMsg {
+ admin: Address::generate(&env),
+ relayer: Address::generate(&env),
+ native_token: env.register_stellar_asset_contract(Address::generate(&env)),
+ xcall_address: Address::generate(&env),
+ upgrade_authority: Address::generate(&env),
+ }
+}
+
+
+#[test]
+fn test_initialize() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let admin = client.get_admin();
+ assert_eq!(admin, ctx.admin)
+}
+
+#[test]
+#[should_panic(expected = "HostError: Error(Contract, #3)")]
+fn test_initialize_fail_on_double_initialize() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+ ctx.init_context(&client);
+
+ client.initialize(&get_dummy_initialize_msg(&ctx.env));
+}
+
+#[test]
+fn test_set_admin() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let new_admin = Address::generate(&ctx.env);
+ client.set_admin(&new_admin);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.admin.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ symbol_short!("set_admin"),
+ (new_admin.clone(),).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ )
+}
+
+#[test]
+#[should_panic]
+fn test_set_admin_fail() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let new_admin = Address::generate(&ctx.env);
+ client.set_admin(&new_admin);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.xcall,
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ symbol_short!("set_admin"),
+ (new_admin.clone(),).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ )
+}
+
+#[test]
+fn test_set_upgrade_authority() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+ ctx.init_context(&client);
+
+ let new_upgrade_authority = Address::generate(&ctx.env);
+ client.set_upgrade_authority(&new_upgrade_authority);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.upgrade_authority.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ ctx.contract.clone(),
+ Symbol::new(&ctx.env, "set_upgrade_authority"),
+ (&new_upgrade_authority,).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+
+ let autorhity = client.get_upgrade_authority();
+ assert_eq!(autorhity, new_upgrade_authority);
+}
+
+#[test]
+fn test_set_fee() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let nid = String::from_str(&ctx.env, "icon");
+ client.set_fee(&nid, &10, &10);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.relayer,
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ symbol_short!("set_fee"),
+ (nid.clone(), 10_u128, 10_u128).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+ assert_eq!(client.get_fee(&nid, &true), 20);
+ assert_eq!(client.get_fee(&nid, &false), 10);
+}
+
+#[test]
+fn test_claim_fees() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let token_client = token::Client::new(&ctx.env, &ctx.native_token);
+ let asset_client = token::StellarAssetClient::new(&ctx.env, &ctx.native_token);
+
+ asset_client.mint(&ctx.contract, &1000);
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.token_admin,
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ ctx.native_token.clone(),
+ symbol_short!("mint"),
+ (&ctx.contract.clone(), 1000_i128,).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+ assert_eq!(token_client.balance(&ctx.contract), 1000);
+
+ client.claim_fees();
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.relayer.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ Symbol::new(&ctx.env, "claim_fees"),
+ ().into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+ assert_eq!(token_client.balance(&ctx.relayer), 1000);
+ assert_eq!(token_client.balance(&ctx.contract), 0);
+ assert_eq!(ctx.env.auths(), std::vec![]);
+}
+
+#[test]
+fn test_send_message() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+ ctx.init_send_message(&client);
+
+ let tx_origin = Address::generate(&ctx.env);
+
+ let asset_client = token::StellarAssetClient::new(&ctx.env, &ctx.native_token);
+ asset_client.mint(&tx_origin, &1000);
+
+ let msg = Bytes::from_array(&ctx.env, &[1, 2, 3]);
+ client.send_message(&tx_origin, &ctx.nid, &1, &msg);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![
+ (
+ ctx.xcall.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ Symbol::new(&ctx.env, "send_message"),
+ (tx_origin.clone(), ctx.nid.clone(), 1_i64, msg.clone()).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ ),
+ (
+ tx_origin.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ ctx.native_token.clone(),
+ Symbol::new(&ctx.env, "transfer"),
+ (tx_origin.clone(), ctx.contract.clone(), 200_i128).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )
+ ]
+ );
+
+ let emit_msg = SendMsgEvent {
+ targetNetwork: ctx.nid.clone(),
+ connSn: 1_u128,
+ msg: msg.clone(),
+ };
+ let event = vec![&ctx.env, ctx.env.events().all().last_unchecked()];
+ assert_eq!(
+ event,
+ vec![
+ &ctx.env,
+ (
+ client.address.clone(),
+ ("Message",).into_val(&ctx.env),
+ emit_msg.into_val(&ctx.env)
+ )
+ ]
+ )
+}
+
+#[test]
+#[should_panic(expected = "HostError: Error(Contract, #10)")]
+fn test_send_message_fail_for_insufficient_fee() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+ ctx.init_send_message(&client);
+
+ let sender = Address::generate(&ctx.env);
+
+ let asset_client = token::StellarAssetClient::new(&ctx.env, &ctx.native_token);
+ asset_client.mint(&sender, &100);
+
+ let msg = Bytes::from_array(&ctx.env, &[1, 2, 3]);
+ client.send_message(&sender, &ctx.nid, &1, &msg);
+}
+
+#[test]
+fn test_get_receipt_returns_false() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ let sequence_no = 1;
+ let receipt = client.get_receipt(&ctx.nid, &sequence_no);
+ assert_eq!(receipt, false);
+
+ ctx.env.as_contract(&ctx.contract, || {
+ storage::store_receipt(&ctx.env, ctx.nid.clone(), sequence_no);
+ });
+
+ let receipt = client.get_receipt(&ctx.nid, &sequence_no);
+ assert_eq!(receipt, true)
+}
+
+#[test]
+fn test_add_validator() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let val1 = [4, 174, 54, 168, 191, 216, 207, 101, 134, 243, 76, 104, 133, 40, 137, 72, 53, 245, 231, 193, 157, 54, 104, 155, 172, 84, 96, 101, 107, 97, 60, 94, 171, 241, 250, 152, 34, 18, 170, 39, 202, 236, 226, 58, 39, 8, 235, 60, 137, 54, 225, 50, 185, 253, 130, 197, 174, 226, 170, 75, 6, 145, 123, 87, 19];
+ let val2 = [4, 91, 65, 155, 222, 192, 210, 187, 193, 108, 232, 174, 20, 79, 248, 232, 37, 18, 63, 208, 203, 62, 54, 208, 7, 91, 109, 141, 229, 170, 181, 51, 136, 172, 143, 180, 194, 138, 138, 56, 67, 243, 7, 60, 218, 164, 12, 148, 63, 116, 115, 127, 192, 206, 164, 169, 95, 135, 119, 138, 255, 172, 115, 129, 144];
+ let val3 = [4, 248, 192, 175, 198, 228, 250, 20, 158, 23, 251, 176, 244, 208, 150, 71, 151, 27, 208, 22, 41, 30, 154, 198, 109, 10, 112, 142, 200, 47, 200, 213, 210, 172, 135, 141, 129, 183, 211, 241, 211, 127, 16, 19, 67, 159, 195, 235, 88, 164, 223, 47, 128, 47, 147, 28, 121, 28, 93, 129, 176, 144, 52, 243, 55];
+
+ let mut validators = Vec::new(&ctx.env);
+ validators.push_back(BytesN::from_array(&ctx.env, &val1));
+ validators.push_back(BytesN::from_array(&ctx.env, &val2));
+ validators.push_back(BytesN::from_array(&ctx.env, &val3));
+ client.update_validators(&validators, &3_u32);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.admin.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ Symbol::new(&ctx.env, "update_validators"),
+ (validators.clone(), 3_u32,).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+
+ assert_eq!(
+ client.get_validators(),
+ validators
+ );
+}
+
+
+#[test]
+fn test_set_threshold() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let val1 = [4, 174, 54, 168, 191, 216, 207, 101, 134, 243, 76, 104, 133, 40, 137, 72, 53, 245, 231, 193, 157, 54, 104, 155, 172, 84, 96, 101, 107, 97, 60, 94, 171, 241, 250, 152, 34, 18, 170, 39, 202, 236, 226, 58, 39, 8, 235, 60, 137, 54, 225, 50, 185, 253, 130, 197, 174, 226, 170, 75, 6, 145, 123, 87, 19];
+ let val2 = [4, 91, 65, 155, 222, 192, 210, 187, 193, 108, 232, 174, 20, 79, 248, 232, 37, 18, 63, 208, 203, 62, 54, 208, 7, 91, 109, 141, 229, 170, 181, 51, 136, 172, 143, 180, 194, 138, 138, 56, 67, 243, 7, 60, 218, 164, 12, 148, 63, 116, 115, 127, 192, 206, 164, 169, 95, 135, 119, 138, 255, 172, 115, 129, 144];
+ let val3 = [4, 248, 192, 175, 198, 228, 250, 20, 158, 23, 251, 176, 244, 208, 150, 71, 151, 27, 208, 22, 41, 30, 154, 198, 109, 10, 112, 142, 200, 47, 200, 213, 210, 172, 135, 141, 129, 183, 211, 241, 211, 127, 16, 19, 67, 159, 195, 235, 88, 164, 223, 47, 128, 47, 147, 28, 121, 28, 93, 129, 176, 144, 52, 243, 55];
+
+ let mut validators = Vec::new(&ctx.env);
+ validators.push_back(BytesN::from_array(&ctx.env, &val1));
+ validators.push_back(BytesN::from_array(&ctx.env, &val2));
+ validators.push_back(BytesN::from_array(&ctx.env, &val3));
+ client.update_validators(&validators, &3_u32);
+
+ let threshold: u32 = 2_u32;
+ client.set_validators_threshold(&threshold);
+
+ assert_eq!(
+ ctx.env.auths(),
+ std::vec![(
+ ctx.admin.clone(),
+ AuthorizedInvocation {
+ function: AuthorizedFunction::Contract((
+ client.address.clone(),
+ Symbol::new(&ctx.env, "set_validators_threshold"),
+ (threshold,).into_val(&ctx.env)
+ )),
+ sub_invocations: std::vec![]
+ }
+ )]
+ );
+ assert_eq!(client.get_validators_threshold(), threshold);
+
+ let threshold: u32 = 3_u32;
+ client.set_validators_threshold(&threshold);
+ assert_eq!(client.get_validators_threshold(), threshold);
+}
+
+
+#[test]
+fn test_receive_message() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let val1 = [4, 174, 54, 168, 191, 216, 207, 101, 134, 243, 76, 104, 133, 40, 137, 72, 53, 245, 231, 193, 157, 54, 104, 155, 172, 84, 96, 101, 107, 97, 60, 94, 171, 241, 250, 152, 34, 18, 170, 39, 202, 236, 226, 58, 39, 8, 235, 60, 137, 54, 225, 50, 185, 253, 130, 197, 174, 226, 170, 75, 6, 145, 123, 87, 19];
+ let val2 = [4, 91, 65, 155, 222, 192, 210, 187, 193, 108, 232, 174, 20, 79, 248, 232, 37, 18, 63, 208, 203, 62, 54, 208, 7, 91, 109, 141, 229, 170, 181, 51, 136, 172, 143, 180, 194, 138, 138, 56, 67, 243, 7, 60, 218, 164, 12, 148, 63, 116, 115, 127, 192, 206, 164, 169, 95, 135, 119, 138, 255, 172, 115, 129, 144];
+ let val3 = [4, 248, 192, 175, 198, 228, 250, 20, 158, 23, 251, 176, 244, 208, 150, 71, 151, 27, 208, 22, 41, 30, 154, 198, 109, 10, 112, 142, 200, 47, 200, 213, 210, 172, 135, 141, 129, 183, 211, 241, 211, 127, 16, 19, 67, 159, 195, 235, 88, 164, 223, 47, 128, 47, 147, 28, 121, 28, 93, 129, 176, 144, 52, 243, 55];
+
+ let mut validators = Vec::new(&ctx.env);
+ validators.push_back(BytesN::from_array(&ctx.env, &val1));
+ validators.push_back(BytesN::from_array(&ctx.env, &val2));
+ validators.push_back(BytesN::from_array(&ctx.env, &val3));
+ client.update_validators(&validators, &1_u32);
+
+ let conn_sn = 456456_u128;
+ let msg = Bytes::from_array(&ctx.env,&[104, 101, 108, 108, 111]);
+ let src_network = String::from_str(&ctx.env, "0x2.icon");
+
+ let mut signatures = Vec::new(&ctx.env);
+ signatures.push_back(BytesN::from_array(&ctx.env, &[35, 247, 49, 199, 251, 53, 83, 51, 115, 148, 35, 48, 85, 203, 185, 236, 5, 171, 221, 29, 247, 203, 190, 195, 208, 218, 204, 237, 88, 191, 91, 75, 48, 87, 108, 161, 75, 234, 147, 234, 65, 134, 233, 32, 249, 159, 43, 159, 86, 211, 1, 117, 176, 167, 53, 99, 34, 243, 165, 215, 93, 232, 67, 184, 27]));
+
+ client.recv_message_with_signatures(&src_network, &conn_sn, &msg, &signatures);
+}
+
+#[test]
+#[should_panic(expected = "HostError: Error(Contract, #11)")]
+fn test_receive_message_less_signatures() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let val1 = [4, 174, 54, 168, 191, 216, 207, 101, 134, 243, 76, 104, 133, 40, 137, 72, 53, 245, 231, 193, 157, 54, 104, 155, 172, 84, 96, 101, 107, 97, 60, 94, 171, 241, 250, 152, 34, 18, 170, 39, 202, 236, 226, 58, 39, 8, 235, 60, 137, 54, 225, 50, 185, 253, 130, 197, 174, 226, 170, 75, 6, 145, 123, 87, 19];
+ let val2 = [4, 91, 65, 155, 222, 192, 210, 187, 193, 108, 232, 174, 20, 79, 248, 232, 37, 18, 63, 208, 203, 62, 54, 208, 7, 91, 109, 141, 229, 170, 181, 51, 136, 172, 143, 180, 194, 138, 138, 56, 67, 243, 7, 60, 218, 164, 12, 148, 63, 116, 115, 127, 192, 206, 164, 169, 95, 135, 119, 138, 255, 172, 115, 129, 144];
+ let val3 = [4, 248, 192, 175, 198, 228, 250, 20, 158, 23, 251, 176, 244, 208, 150, 71, 151, 27, 208, 22, 41, 30, 154, 198, 109, 10, 112, 142, 200, 47, 200, 213, 210, 172, 135, 141, 129, 183, 211, 241, 211, 127, 16, 19, 67, 159, 195, 235, 88, 164, 223, 47, 128, 47, 147, 28, 121, 28, 93, 129, 176, 144, 52, 243, 55];
+
+ let mut validators = Vec::new(&ctx.env);
+ validators.push_back(BytesN::from_array(&ctx.env, &val1));
+ validators.push_back(BytesN::from_array(&ctx.env, &val2));
+ validators.push_back(BytesN::from_array(&ctx.env, &val3));
+ client.update_validators(&validators, &2_u32);
+
+ let conn_sn = 456456_u128;
+ let msg = Bytes::from_array(&ctx.env,&[104, 101, 108, 108, 111]);
+ let src_network = String::from_str(&ctx.env, "0x2.icon");
+
+ let mut signatures = Vec::new(&ctx.env);
+ signatures.push_back(BytesN::from_array(&ctx.env, &[35, 247, 49, 199, 251, 53, 83, 51, 115, 148, 35, 48, 85, 203, 185, 236, 5, 171, 221, 29, 247, 203, 190, 195, 208, 218, 204, 237, 88, 191, 91, 75, 48, 87, 108, 161, 75, 234, 147, 234, 65, 134, 233, 32, 249, 159, 43, 159, 86, 211, 1, 117, 176, 167, 53, 99, 34, 243, 165, 215, 93, 232, 67, 184, 27]));
+
+ client.recv_message_with_signatures(&src_network, &conn_sn, &msg, &signatures);
+}
+
+#[test]
+#[should_panic(expected = "HostError: Error(Contract, #11)")]
+fn test_receive_message_with_invalid_signature() {
+ let ctx = TestContext::default();
+ let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract);
+
+ ctx.init_context(&client);
+
+ let val1 = [4, 174, 54, 168, 191, 216, 207, 101, 134, 243, 76, 104, 133, 40, 137, 72, 53, 245, 231, 193, 157, 54, 104, 155, 172, 84, 96, 101, 107, 97, 60, 94, 171, 241, 250, 152, 34, 18, 170, 39, 202, 236, 226, 58, 39, 8, 235, 60, 137, 54, 225, 50, 185, 253, 130, 197, 174, 226, 170, 75, 6, 145, 123, 87, 19];
+ let val2 = [4, 91, 65, 155, 222, 192, 210, 187, 193, 108, 232, 174, 20, 79, 248, 232, 37, 18, 63, 208, 203, 62, 54, 208, 7, 91, 109, 141, 229, 170, 181, 51, 136, 172, 143, 180, 194, 138, 138, 56, 67, 243, 7, 60, 218, 164, 12, 148, 63, 116, 115, 127, 192, 206, 164, 169, 95, 135, 119, 138, 255, 172, 115, 129, 144];
+ let val3 = [4, 248, 192, 175, 198, 228, 250, 20, 158, 23, 251, 176, 244, 208, 150, 71, 151, 27, 208, 22, 41, 30, 154, 198, 109, 10, 112, 142, 200, 47, 200, 213, 210, 172, 135, 141, 129, 183, 211, 241, 211, 127, 16, 19, 67, 159, 195, 235, 88, 164, 223, 47, 128, 47, 147, 28, 121, 28, 93, 129, 176, 144, 52, 243, 55];
+
+ let mut validators = Vec::new(&ctx.env);
+ validators.push_back(BytesN::from_array(&ctx.env, &val1));
+ validators.push_back(BytesN::from_array(&ctx.env, &val2));
+ validators.push_back(BytesN::from_array(&ctx.env, &val3));
+ client.update_validators(&validators, &1_u32);
+
+ let conn_sn = 456456_u128;
+ let msg = Bytes::from_array(&ctx.env,&[104, 100, 108, 108, 111]);
+ let src_network = String::from_str(&ctx.env, "0x2.icon");
+
+ let mut signatures = Vec::new(&ctx.env);
+ signatures.push_back(BytesN::from_array(&ctx.env, &[35, 247, 49, 199, 251, 53, 83, 51, 115, 148, 35, 48, 85, 203, 185, 236, 5, 171, 221, 29, 247, 203, 190, 195, 208, 218, 204, 237, 88, 191, 91, 75, 48, 87, 108, 161, 75, 234, 147, 234, 65, 134, 233, 32, 249, 159, 43, 159, 86, 211, 1, 117, 176, 167, 53, 99, 34, 243, 165, 215, 93, 232, 67, 184, 27]));
+
+ client.recv_message_with_signatures(&src_network, &conn_sn, &msg, &signatures);
+}
+
diff --git a/contracts/soroban/contracts/cluster-connection/src/types.rs b/contracts/soroban/contracts/cluster-connection/src/types.rs
new file mode 100644
index 000000000..bf5f551d7
--- /dev/null
+++ b/contracts/soroban/contracts/cluster-connection/src/types.rs
@@ -0,0 +1,40 @@
+use soroban_sdk::{contracttype, Address, String};
+
+#[contracttype]
+#[derive(Clone)]
+pub enum StorageKey {
+ Xcall,
+ Relayer,
+ Admin,
+ UpgradeAuthority,
+ Xlm,
+ ConnSn,
+ NetworkFee(String),
+ Receipts(String, u128),
+ Validators,
+ ValidatorThreshold
+}
+
+#[contracttype]
+pub struct InitializeMsg {
+ pub relayer: Address,
+ pub admin: Address,
+ pub native_token: Address,
+ pub xcall_address: Address,
+ pub upgrade_authority: Address,
+}
+
+#[contracttype]
+pub struct NetworkFee {
+ pub message_fee: u128,
+ pub response_fee: u128,
+}
+
+impl NetworkFee {
+ pub fn default() -> Self {
+ Self {
+ message_fee: 0,
+ response_fee: 0,
+ }
+ }
+}
diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test.rs
index df83e0625..6be6e7759 100644
--- a/contracts/soroban/contracts/mock-dapp-multi/src/test.rs
+++ b/contracts/soroban/contracts/mock-dapp-multi/src/test.rs
@@ -1,4 +1,4 @@
-#![cfg(test)]
+// #![cfg(test)]
-mod contract;
-pub mod setup;
+// mod contract;
+// pub mod setup;
diff --git a/contracts/soroban/contracts/xcall/src/contract.rs b/contracts/soroban/contracts/xcall/src/contract.rs
index 8d81f6ac2..6c5affce0 100644
--- a/contracts/soroban/contracts/xcall/src/contract.rs
+++ b/contracts/soroban/contracts/xcall/src/contract.rs
@@ -162,4 +162,9 @@ impl Xcall {
pub fn version(env: Env) -> u32 {
storage::get_contract_version(&env)
}
+
+ pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> {
+ storage::extend_instance(&env);
+ Ok(())
+ }
}
diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs
index cfa43a988..1fb56adda 100644
--- a/contracts/soroban/contracts/xcall/src/storage.rs
+++ b/contracts/soroban/contracts/xcall/src/storage.rs
@@ -18,8 +18,8 @@ const LEDGER_BUMP_INSTANCE: u32 = LEDGER_THRESHOLD_INSTANCE + DAY_IN_LEDGERS; //
const LEDGER_THRESHOLD_PERSISTENT: u32 = DAY_IN_LEDGERS * 30; // ~ 30 days
const LEDGER_BUMP_PERSISTENT: u32 = LEDGER_THRESHOLD_PERSISTENT + DAY_IN_LEDGERS; // ~ 31 days
-const LEDGER_THRESHOLD_REQUEST: u32 = DAY_IN_LEDGERS * 7; // ~ 7 days
-const LEDGER_BUMP_REQUEST: u32 = LEDGER_THRESHOLD_REQUEST + DAY_IN_LEDGERS; // ~ 8 days
+const LEDGER_THRESHOLD_REQUEST: u32 = DAY_IN_LEDGERS * 3; // ~ 3 days
+const LEDGER_BUMP_REQUEST: u32 = LEDGER_THRESHOLD_REQUEST + DAY_IN_LEDGERS; // ~ 4 days
pub const MAX_ROLLBACK_SIZE: u64 = 1024;
pub const MAX_DATA_SIZE: u64 = 2048;
@@ -72,10 +72,9 @@ pub fn default_connection(e: &Env, nid: String) -> Result Result bool {
let key = StorageKey::SuccessfulResponses(sn);
let res = e.storage().persistent().get(&key).unwrap_or(false);
- if res {
- extend_persistent(e, &key)
- }
-
res
}
@@ -114,31 +107,21 @@ pub fn get_proxy_request(e: &Env, req_id: u128) -> Result) -> Vec {
let key = StorageKey::PendingRequests(hash);
- let pending_request = e.storage().persistent().get(&key).unwrap_or(Vec::new(&e));
- if pending_request.len() > 0 {
- extend_persistent_request(e, &key);
- }
-
+ let pending_request = e.storage().temporary().get(&key).unwrap_or(Vec::new(&e));
pending_request
}
pub fn get_pending_response(e: &Env, hash: BytesN<32>) -> Vec {
let key = StorageKey::PendingResponses(hash);
- let pending_response = e.storage().persistent().get(&key).unwrap_or(Vec::new(&e));
- if pending_response.len() > 0 {
- extend_persistent_request(e, &key);
- }
-
+ let pending_response = e.storage().temporary().get(&key).unwrap_or(Vec::new(&e));
pending_response
}
@@ -196,53 +179,52 @@ pub fn store_protocol_fee(e: &Env, fee: u128) {
pub fn store_default_connection(e: &Env, nid: String, address: &Address) {
let key = StorageKey::DefaultConnections(nid);
- e.storage().persistent().set(&key, &address);
- extend_persistent(e, &key);
+ e.storage().instance().set(&key, &address);
}
pub fn store_rollback(e: &Env, sn: u128, rollback: &Rollback) {
let key = StorageKey::Rollback(sn);
- e.storage().persistent().set(&key, rollback);
- extend_persistent_request(e, &key)
+ e.storage().temporary().set(&key, rollback);
+ extend_temporary_request(e, &key)
}
pub fn remove_rollback(e: &Env, sn: u128) {
- e.storage().persistent().remove(&StorageKey::Rollback(sn));
+ e.storage().temporary().remove(&StorageKey::Rollback(sn));
}
pub fn store_proxy_request(e: &Env, req_id: u128, request: &CSMessageRequest) {
let key = StorageKey::ProxyRequest(req_id);
- e.storage().persistent().set(&key, request);
- extend_persistent_request(e, &key)
+ e.storage().temporary().set(&key, request);
+ extend_temporary_request(e, &key)
}
pub fn remove_proxy_request(e: &Env, req_id: u128) {
e.storage()
- .persistent()
+ .temporary()
.remove(&StorageKey::ProxyRequest(req_id))
}
pub fn store_pending_request(e: &Env, hash: BytesN<32>, sources: &Vec) {
let key = StorageKey::PendingRequests(hash.clone());
- e.storage().persistent().set(&key, sources);
- extend_persistent_request(e, &key)
+ e.storage().temporary().set(&key, sources);
+ extend_temporary_request(e, &key)
}
pub fn remove_pending_request(e: &Env, hash: BytesN<32>) {
e.storage()
- .persistent()
+ .temporary()
.remove(&StorageKey::PendingRequests(hash))
}
pub fn store_pending_response(e: &Env, hash: BytesN<32>, sources: &Vec) {
let key = StorageKey::PendingResponses(hash);
- e.storage().persistent().set(&key, sources);
- extend_persistent_request(e, &key)
+ e.storage().temporary().set(&key, sources);
+ extend_temporary_request(e, &key)
}
pub fn remove_pending_response(e: &Env, hash: BytesN<32>) {
e.storage()
- .persistent()
+ .temporary()
.remove(&StorageKey::PendingResponses(hash))
}
@@ -275,8 +257,9 @@ pub fn extend_persistent(e: &Env, key: &StorageKey) {
.extend_ttl(key, LEDGER_THRESHOLD_PERSISTENT, LEDGER_BUMP_PERSISTENT);
}
-pub fn extend_persistent_request(e: &Env, key: &StorageKey) {
+pub fn extend_temporary_request(e: &Env, key: &StorageKey) {
e.storage()
- .persistent()
+ .temporary()
.extend_ttl(key, LEDGER_THRESHOLD_REQUEST, LEDGER_BUMP_REQUEST);
}
+
diff --git a/contracts/soroban/libs/soroban-rlp/src/decoder.rs b/contracts/soroban/libs/soroban-rlp/src/decoder.rs
index 37b83405b..9d05760db 100644
--- a/contracts/soroban/libs/soroban-rlp/src/decoder.rs
+++ b/contracts/soroban/libs/soroban-rlp/src/decoder.rs
@@ -131,6 +131,9 @@ pub fn decode_u64(env: &Env, bytes: Bytes) -> u64 {
}
pub fn decode_u128(env: &Env, bytes: Bytes) -> u128 {
+ if bytes.len() == 1 {
+ return bytes_to_u128(bytes);
+ }
let decoded = decode(&env, bytes);
bytes_to_u128(decoded)
}
diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move
index 5e2b93b1f..1e63672bf 100644
--- a/contracts/sui/libs/sui_rlp/sources/encoder.move
+++ b/contracts/sui/libs/sui_rlp/sources/encoder.move
@@ -49,7 +49,7 @@ module sui_rlp::encoder {
vector::append(&mut encoded_list,result);
} else {
- let length_bytes = utils::to_bytes_u64(len,false);
+ let length_bytes = utils::to_bytes_u64_sign(len,false);
let prefix = (0xf7 + vector::length(&length_bytes)) as u8;
vector::push_back(&mut encoded_list, prefix);
vector::append(&mut encoded_list, length_bytes);
@@ -69,7 +69,7 @@ module sui_rlp::encoder {
let len_u8=(len as u8);
vector::push_back(&mut length_info,(offset+len_u8));
}else {
- let length_bytes=utils::to_bytes_u64(len,false);
+ let length_bytes=utils::to_bytes_u64_sign(len,false);
let length_byte_len=vector::length(&length_bytes);
let length_byte_len=offset+(length_byte_len as u8);
vector::push_back(&mut length_info,length_byte_len);
@@ -86,19 +86,19 @@ module sui_rlp::encoder {
}
public fun encode_u32(num:u32):vector{
- let vec= utils::to_bytes_u32(num,true);
+ let vec= utils::to_bytes_u32_sign(num,true);
encode(&vec)
}
public fun encode_u64(num:u64):vector{
- let vec= utils::to_bytes_u64(num,true);
+ let vec= utils::to_bytes_u64_sign(num,true);
encode(&vec)
}
public fun encode_u128(num:u128):vector{
- let vec= utils::to_bytes_u128(num,true);
+ let vec= utils::to_bytes_u128_sign(num,true);
encode(&vec)
}
diff --git a/contracts/sui/libs/sui_rlp/sources/utils.move b/contracts/sui/libs/sui_rlp/sources/utils.move
index af37a07d3..53529c3d2 100644
--- a/contracts/sui/libs/sui_rlp/sources/utils.move
+++ b/contracts/sui/libs/sui_rlp/sources/utils.move
@@ -41,18 +41,35 @@ module sui_rlp::utils {
}
- public fun to_bytes_u128(number:u128,signed:bool):vector{
+ //Deprecated
+ public fun to_bytes_u128(number:u128):vector{
+ let bytes=bcs::to_bytes(&number);
+ to_signed_bytes(bytes,true)
+ }
+
+ public fun to_bytes_u128_sign(number:u128,signed:bool):vector{
let bytes=bcs::to_bytes(&number);
to_signed_bytes(bytes,signed)
}
+ //Deprecated
+ public fun to_bytes_u64(number:u64):vector{
+ let bytes=bcs::to_bytes(&number);
+ to_signed_bytes(bytes,true)
+ }
- public fun to_bytes_u64(number:u64,signed:bool):vector{
+ public fun to_bytes_u64_sign(number:u64,signed:bool):vector{
let bytes=bcs::to_bytes(&number);
to_signed_bytes(bytes,signed)
}
+
+ //Deprecated
+ public fun to_bytes_u32(number: u32): vector {
+ let bytes=bcs::to_bytes(&number);
+ to_signed_bytes(bytes,true)
+ }
- public fun to_bytes_u32(number: u32,signed:bool): vector {
+ public fun to_bytes_u32_sign(number: u32,signed:bool): vector {
let bytes=bcs::to_bytes(&number);
to_signed_bytes(bytes,signed)
}
@@ -120,7 +137,7 @@ module sui_rlp::utils_test {
#[test]
fun test_u32_conversion() {
let num= (122 as u32);
- let bytes= utils::to_bytes_u32(num,true);
+ let bytes= utils::to_bytes_u32_sign(num,true);
let converted=utils::from_bytes_u32(&bytes);
assert!(num==converted,0x01);
@@ -129,7 +146,7 @@ module sui_rlp::utils_test {
#[test]
fun test_u64_conversion() {
let num= (55000 as u64);
- let bytes= utils::to_bytes_u64(num,true);
+ let bytes= utils::to_bytes_u64_sign(num,true);
let converted=utils::from_bytes_u64(&bytes);
std::debug::print(&bytes);
std::debug::print(&converted);
@@ -140,7 +157,7 @@ module sui_rlp::utils_test {
#[test]
fun test_u128_conversion() {
let num= (1222223333 as u128);
- let bytes= utils::to_bytes_u128(num,true);
+ let bytes= utils::to_bytes_u128_sign(num,true);
std::debug::print(&bytes);
let converted=utils::from_bytes_u128(&bytes);
std::debug::print(&converted);