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);