From 06089373ebe3c76d0f24d558b498ae1e58e59ea0 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Fri, 16 Aug 2024 21:46:59 +0545 Subject: [PATCH 01/75] feat: add multisig contract --- contracts/sui/multisig/Move.lock | 26 +++ contracts/sui/multisig/Move.toml | 37 ++++ contracts/sui/multisig/sources/base64.move | 134 ++++++++++++ contracts/sui/multisig/sources/multisig.move | 207 ++++++++++++++++++ .../sui/multisig/tests/multisig_tests.move | 19 ++ 5 files changed, 423 insertions(+) create mode 100644 contracts/sui/multisig/Move.lock create mode 100644 contracts/sui/multisig/Move.toml create mode 100644 contracts/sui/multisig/sources/base64.move create mode 100644 contracts/sui/multisig/sources/multisig.move create mode 100644 contracts/sui/multisig/tests/multisig_tests.move diff --git a/contracts/sui/multisig/Move.lock b/contracts/sui/multisig/Move.lock new file mode 100644 index 00000000..940ee6ce --- /dev/null +++ b/contracts/sui/multisig/Move.lock @@ -0,0 +1,26 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 2 +manifest_digest = "0BAC026A0C518E4F4E63B8A7BB1FA36291E16B8919911B03E105CDD71EA40B5E" +deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" +dependencies = [ + { name = "Sui" }, +] + +[[move.package]] +name = "MoveStdlib" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/move-stdlib" } + +[[move.package]] +name = "Sui" +source = { git = "https://github.com/MystenLabs/sui.git", rev = "framework/testnet", subdir = "crates/sui-framework/packages/sui-framework" } + +dependencies = [ + { name = "MoveStdlib" }, +] + +[move.toolchain-version] +compiler-version = "1.30.3" +edition = "2024.beta" +flavor = "sui" diff --git a/contracts/sui/multisig/Move.toml b/contracts/sui/multisig/Move.toml new file mode 100644 index 00000000..0299139b --- /dev/null +++ b/contracts/sui/multisig/Move.toml @@ -0,0 +1,37 @@ +[package] +name = "multisig" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move +# license = "" # e.g., "MIT", "GPL", "Apache 2.0" +# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"] + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } + +# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`. +# Revision can be a branch, a tag, and a commit hash. +# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" } + +# For local dependencies use `local = path`. Path is relative to the package root +# Local = { local = "../path/to" } + +# To resolve a version conflict and force a specific version for dependency +# override use `override = true` +# Override = { local = "../conflicting/version", override = true } + +[addresses] +multisig = "0x0" + +# Named addresses will be accessible in Move as `@name`. They're also exported: +# for example, `std = "0x1"` is exported by the Standard Library. +# alice = "0xA11CE" + +[dev-dependencies] +# The dev-dependencies section allows overriding dependencies for `--test` and +# `--dev` modes. You can introduce test-only dependencies here. +# Local = { local = "../path/to/dev-build" } + +[dev-addresses] +# The dev-addresses section allows overwriting named addresses for the `--test` +# and `--dev` modes. +# alice = "0xB0B" + diff --git a/contracts/sui/multisig/sources/base64.move b/contracts/sui/multisig/sources/base64.move new file mode 100644 index 00000000..d2488003 --- /dev/null +++ b/contracts/sui/multisig/sources/base64.move @@ -0,0 +1,134 @@ +module multisig::base64 { + use std::vector::{Self}; + use std::string::{Self,String}; + use sui::vec_map::{Self, VecMap}; + + const BASE64_CHARS: vector = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const PADDING_CHAR: vector = b"="; + + +public fun encode(input:&vector):String { + + let mut output:vector = vector::empty(); + let mut i=0; + while (i > 18) & 0x3F) as u64)); + output.push_back(*BASE64_CHARS.borrow(((triple >> 12) & 0x3F) as u64)); + + if (i + 1 < input.length()) { + output.push_back(*BASE64_CHARS.borrow(((triple >> 6) & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + if (i + 2 < input.length()) { + output.push_back(*BASE64_CHARS.borrow((triple & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + i =i+ 3; + }; + string::utf8(output) +} + +public fun decode(input:&String):vector{ + let char_index=get_char_map(); + let mut output = vector::empty(); + let input_bytes = input.as_bytes(); + let mut i = 0; + while( i < input_bytes.length()){ + let b1 = *char_index.get(input_bytes.borrow(i)); + let b2 = *char_index.get(input_bytes.borrow(i + 1)); + let b3 = if (i + 2 < input_bytes.length()) { + let key=input_bytes.borrow(i+2); + let val:u32 = if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + 64 + }; + let b4 = if (i + 3 < input_bytes.length()) { + let key=input_bytes.borrow(i+3); + let val:u32= if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + + 64 + }; + + let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; + + output.push_back(((triple >> 16) & 0xFF) as u8); + + if (b3 != 64) { + output.push_back(((triple >> 8) & 0xFF) as u8); + }; + if (b4 != 64) { + output.push_back((triple & 0xFF) as u8); + }; + + i = i+4; + + }; + output + +} + + + + fun get_char_map():VecMap{ + let mut char_map = vec_map::empty(); + let mut i:u64=0; + while( i < BASE64_CHARS.length()){ + let c=*BASE64_CHARS.borrow(i); + char_map.insert(c,(i as u32)); + i=i+1; + }; + char_map + } +} + + +#[test_only] +module multisig::base64_tests { + use sui::hash::{Self}; + use multisig::base64::{Self}; + #[test] + fun test_base64(){ + let input = b"Hello, World!"; + let encoded = base64::encode(&input); + assert!(encoded.as_bytes()==b"SGVsbG8sIFdvcmxkIQ=="); + + let decoded = base64::decode(&encoded); + std::debug::print(&encoded); + assert!(decoded==input); + + } + + +} + + + diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move new file mode 100644 index 00000000..a772605f --- /dev/null +++ b/contracts/sui/multisig/sources/multisig.move @@ -0,0 +1,207 @@ + +module multisig::multisig { + use std::vector::{Self}; + use sui::linked_table::{Self, LinkedTable}; + use sui::types as sui_types; + use std::string::{Self, String}; + use sui::event; + use sui::hash::{Self}; + use sui::vec_map::{Self, VecMap}; + use sui::table::{Table,Self}; + use sui::bcs::{Self}; + use sui::address::{Self}; + use multisig::base64::{Self}; + + + public struct Signer has store{ + pub_key:vector, + sui_address:address, + weight:u8 + } + + public struct MultisigWallet has store { + multisig_address:address, + signers:vector, + threshold:u16, + + } + + public struct Proposal has store{ + id:u64, + title:String, + multisig_address:address, + tx_data:vector, + } + + public struct Vote has store{ + id:u64, + signature:vector, + pub_key:address, + } + + public struct VoteKey has store,drop,copy{ + proposal_id:u64, + sui_address:address, + } + + + public struct Storage has key,store{ + id:UID, + wallets:VecMap, + wallet_proposals:Table>, + proposals:Table, + votes:Table, + proposal_count:u64, + + + + } + public struct AdminCap has key,store { + id: UID + } + + + fun init(ctx: &mut TxContext) { + let admin = AdminCap { + id: object::new(ctx), + }; + let storage = Storage { + id:object::new(ctx), + wallets:vec_map::empty(), + wallet_proposals: table::new(ctx), + proposals:table::new(ctx), + votes: table::new(ctx), + proposal_count:0u64, + }; + transfer::transfer(admin, tx_context::sender(ctx)); + transfer::share_object(storage); + + } + + + + public fun create_multisig_address(pubkeys:vector>,weights:vector,threshold:u16):address{ + let mut bytes= vector::empty(); + bytes.push_back(0x03); + let threshold_bytes=bcs::to_bytes(&threshold); + bytes.append(threshold_bytes); + let mut i=0; + while(i < pubkeys.length()){ + bytes.append(*pubkeys.borrow(i)); + bytes.push_back(*weights.borrow(i)); + i=i+1; + }; + + let address_bytes=hash::blake2b256(&bytes); + address::from_bytes(address_bytes) + + } + + entry fun register_wallet(storage:&mut Storage,pub_keys:vector,weights:vector,threshold:u16){ + let mut pub_keys_bytes:vector> = vector::empty(); + let mut signers:vector = vector::empty(); + let mut i=0; + while(i < pub_keys.length()){ + let bytes=base64::decode(pub_keys.borrow(i)); + pub_keys_bytes.push_back(bytes); + let sui_address= address::from_bytes(hash::blake2b256(&bytes)); + let signer_1= Signer { + pub_key:bytes, + sui_address:sui_address, + weight:*weights.borrow(i), + }; + signers.push_back(signer_1); + i=i+1; + }; + let multisig_addr= create_multisig_address(pub_keys_bytes,weights,threshold); + let multisig_wallet= MultisigWallet{ + multisig_address:multisig_addr, + signers:signers, + threshold:threshold, + }; + storage.wallets.insert(multisig_addr,multisig_wallet); + } + + entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes:vector,multisig_address:address,ctx:&mut TxContext){ + let wallet=storage.wallets.get(&multisig_address); + assert!(only_member(wallet,ctx.sender())==true); + let proposal_id=get_proposal_id(storage); + let proposal= Proposal{ + id:proposal_id, + title:title, + multisig_address:multisig_address, + tx_data:tx_bytes, + + }; + storage.proposals.add(proposal_id,proposal); + storage.wallet_proposals.borrow_mut(multisig_address).push_back(proposal_id); + + } + + entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,signature:vector,ctx:&mut TxContext){ + + } + + fun only_member(wallet:&MultisigWallet,caller:address):bool{ + let mut i=0; + let mut is_member=false; + while(i < wallet.signers.length() && is_member==false){ + if (wallet.signers.borrow(i).sui_address==caller){ + is_member=true; + }; + }; + is_member + + } + + fun get_proposal_id(storage:&mut Storage):u64{ + let count=storage.proposal_count+1; + storage.proposal_count=count; + count + } + +} +#[test_only] +module multisig::tests { + use sui::hash::{Self}; + use multisig::multisig::{create_multisig_address}; + #[test] + fun test_public__key_address(){ + let pubkey = x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + let sui_address=hash::blake2b256(&pubkey); + assert!(x"29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589"==sui_address); + + + } + + #[test] + fun test_multisig_address(){ + let mut public_keys:vector> =vector::empty(); + public_keys.push_back(x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + public_keys.push_back(x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"); + public_keys.push_back(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + public_keys.push_back(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"); + public_keys.push_back(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + let mut weights:vector = vector::empty(); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + weights.push_back(1u8); + let threshold:u16=3u16; + + let sui_addr= create_multisig_address(public_keys,weights,threshold); + std::debug::print(&sui_addr); + let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + assert!(expected==sui_addr); + + + + } + + + + +} + + diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move new file mode 100644 index 00000000..94a5355c --- /dev/null +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -0,0 +1,19 @@ + +#[test_only] +module multisig::multisig_tests { + // uncomment this line to import the module + use multisig::multisig; + + const ENotImplemented: u64 = 0; + + #[test] + fun test_create_multisig() { + // let pub_keys=[]; + } + + #[test, expected_failure(abort_code = ::multisig::multisig_tests::ENotImplemented)] + fun test_multisig_fail() { + abort ENotImplemented + } +} + From e25f351ebaa7ab53f5632fe16fcf18322e931bb9 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Wed, 21 Aug 2024 21:25:40 +0545 Subject: [PATCH 02/75] fix: add tests --- contracts/sui/multisig/sources/base64.move | 10 +- contracts/sui/multisig/sources/multisig.move | 416 +++++++++++++++++- .../sui/multisig/tests/multisig_tests.move | 131 +++++- 3 files changed, 531 insertions(+), 26 deletions(-) diff --git a/contracts/sui/multisig/sources/base64.move b/contracts/sui/multisig/sources/base64.move index d2488003..551fc0c2 100644 --- a/contracts/sui/multisig/sources/base64.move +++ b/contracts/sui/multisig/sources/base64.move @@ -118,12 +118,12 @@ module multisig::base64_tests { #[test] fun test_base64(){ let input = b"Hello, World!"; - let encoded = base64::encode(&input); - assert!(encoded.as_bytes()==b"SGVsbG8sIFdvcmxkIQ=="); + let encoded = base64::encode(&input); + assert!(encoded.as_bytes()==b"SGVsbG8sIFdvcmxkIQ=="); - let decoded = base64::decode(&encoded); - std::debug::print(&encoded); - assert!(decoded==input); + let decoded = base64::decode(&encoded); + std::debug::print(&encoded); + assert!(decoded==input); } diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index a772605f..2aeaecce 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -11,14 +11,36 @@ module multisig::multisig { use sui::bcs::{Self}; use sui::address::{Self}; use multisig::base64::{Self}; + use sui::{ed25519::ed25519_verify}; + use sui::{ecdsa_k1::secp256k1_verify}; + use sui::{ecdsa_r1::secp256r1_verify}; + + /** signature schemes*/ + const FlagED25519 :u8= 0x00; + const FlagSecp256k1 :u8= 0x01; + const FlagSecp256r1 :u8= 0x02; + const FlagMultiSig :u8= 0x03; + + /* hash algorithm*/ + const KECCAK256: u8 = 0x00; + const SHA256: u8 = 0x01; - public struct Signer has store{ + public struct Signer has store,drop{ pub_key:vector, sui_address:address, weight:u8 } + public fun new_signer(pub_key:vector,weight:u8):Signer{ + let sui_address = address::from_bytes(hash::blake2b256(&pub_key)); + Signer { + pub_key, + sui_address, + weight + } + } + public struct MultisigWallet has store { multisig_address:address, signers:vector, @@ -26,6 +48,23 @@ module multisig::multisig { } + public fun multisig_address(self:&MultisigWallet):address { + return self.multisig_address + } + + public struct MultiSignature has drop{ + signatures:vector>, + bitmap:u16, + multi_pubkey:MultiPubKey + + + } + + public struct MultiPubKey has drop { + weighted_pubkey:vector>, + threshold:u16 + } + public struct Proposal has store{ id:u64, title:String, @@ -33,10 +72,9 @@ module multisig::multisig { tx_data:vector, } - public struct Vote has store{ - id:u64, + public struct Vote has store,drop{ signature:vector, - pub_key:address, + voter:address, } public struct VoteKey has store,drop,copy{ @@ -55,6 +93,12 @@ module multisig::multisig { + } + public fun get_wallets(self:&Storage):&VecMap{ + &self.wallets + } + public fun get_proposals(self:&Storage):&Table{ + &self.proposals } public struct AdminCap has key,store { id: UID @@ -82,7 +126,7 @@ module multisig::multisig { public fun create_multisig_address(pubkeys:vector>,weights:vector,threshold:u16):address{ let mut bytes= vector::empty(); - bytes.push_back(0x03); + bytes.push_back(FlagMultiSig); let threshold_bytes=bcs::to_bytes(&threshold); bytes.append(threshold_bytes); let mut i=0; @@ -97,19 +141,16 @@ module multisig::multisig { } - entry fun register_wallet(storage:&mut Storage,pub_keys:vector,weights:vector,threshold:u16){ + entry fun register_wallet(storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16){ + assert!(pub_keys.length()==weights.length()); + assert!(threshold>0); let mut pub_keys_bytes:vector> = vector::empty(); let mut signers:vector = vector::empty(); let mut i=0; while(i < pub_keys.length()){ let bytes=base64::decode(pub_keys.borrow(i)); pub_keys_bytes.push_back(bytes); - let sui_address= address::from_bytes(hash::blake2b256(&bytes)); - let signer_1= Signer { - pub_key:bytes, - sui_address:sui_address, - weight:*weights.borrow(i), - }; + let signer_1=new_signer(bytes,*weights.borrow(i)); signers.push_back(signer_1); i=i+1; }; @@ -120,9 +161,10 @@ module multisig::multisig { threshold:threshold, }; storage.wallets.insert(multisig_addr,multisig_wallet); + storage.wallet_proposals.add(multisig_addr, vector::empty()); } - entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes:vector,multisig_address:address,ctx:&mut TxContext){ + entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes:vector,multisig_address:address,ctx:&TxContext){ let wallet=storage.wallets.get(&multisig_address); assert!(only_member(wallet,ctx.sender())==true); let proposal_id=get_proposal_id(storage); @@ -138,8 +180,60 @@ module multisig::multisig { } - entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,signature:vector,ctx:&mut TxContext){ + entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,signature:vector,ctx:&TxContext){ + let proposal = storage.proposals.borrow(proposal_id); + let wallet= storage.wallets.get(&proposal.multisig_address); + assert!(only_member(wallet,ctx.sender())==true); + let (index,pubkey)=get_pubkey(wallet,ctx.sender()); + assert!(index!=0); + assert!(verify_pubkey(&pubkey,&proposal.tx_data,&signature)==true); + let vote_key=VoteKey{ + proposal_id:proposal_id, + sui_address:ctx.sender() + }; + assert!(storage.votes.contains(vote_key)==false); + storage.votes.add(vote_key, Vote{ + signature:signature, + voter:ctx.sender() + }); + + + } + + public fun get_execute_command(storage:&Storage,proposal_id:u64):String{ + let proposal=storage.proposals.borrow(proposal_id); + let wallet=storage.wallets.get(&proposal.multisig_address); + let mut signatures:vector> = vector::empty(); + + let mut i=0; + while( i (); + command.append(b"sui client execute-signed-tx --tx-bytes "); + let tx_data_64= base64::encode(&proposal.tx_data); + command.append(*tx_data_64.as_bytes()); + command.append(b" --signatures "); + command.append(*multisig_serialized_64.as_bytes()); + + string::utf8(command) + + + } fun only_member(wallet:&MultisigWallet,caller:address):bool{ @@ -149,6 +243,7 @@ module multisig::multisig { if (wallet.signers.borrow(i).sui_address==caller){ is_member=true; }; + i=i+1; }; is_member @@ -160,11 +255,186 @@ module multisig::multisig { count } + public fun create_multi_signature(raw_signatures:&vector>,signers:&vector,threshold:u16):MultiSignature{ + let mut bitmap:u16=0; + let mut i:u64=0; + let mut signatures:vector> = vector::empty(); + let mut weighted_pubkey:vector> =vector::empty(); + while( i < raw_signatures.length()){ + let (sig,pub,scheme)=split_signature(raw_signatures.borrow(i)); + let mut index= get_pub_key_index(signers,pub); + assert!(index>0); + index=index-1; + + bitmap =bitmap | (1 << index); + std::debug::print(&bitmap); + let mut full_sig:vector =vector::empty(); + full_sig.push_back(scheme); + full_sig.append(sig); + signatures.push_back(full_sig); + i=i+1; + + + }; + i=0; + while(i < signers.length()){ + let mut pubkey= signers.borrow(i).pub_key; + pubkey.push_back(signers.borrow(i).weight); + weighted_pubkey.push_back(pubkey); + i=i+1; + }; + let multi_pubkey=MultiPubKey { + weighted_pubkey, + threshold + }; + + MultiSignature { + signatures, + bitmap, + multi_pubkey + } + + + + + } + + public fun serialize_multisig(sig:&MultiSignature):vector{ + let mut serialized:vector = vector::empty(); + serialized.push_back(FlagMultiSig); + serialized.push_back(sig.signatures.length() as u8); + let mut i=0; + while(i < sig.signatures.length()){ + serialized.append(*sig.signatures.borrow(i)); + i=i+1; + }; + let bitmap=std::bcs::to_bytes(&sig.bitmap); + serialized.append(bitmap); + serialized.push_back(sig.multi_pubkey.weighted_pubkey.length() as u8); + i=0; + while( i < sig.multi_pubkey.weighted_pubkey.length()){ + serialized.append(*sig.multi_pubkey.weighted_pubkey.borrow(i)); + i=i+1; + }; + serialized.append(std::bcs::to_bytes(&sig.multi_pubkey.threshold)); + serialized + } + + fun get_pub_key_index(signers:&vector,pubkey:vector):u8{ + let mut index:u64=0; + while(index < signers.length()){ + let signer_pubkey=signers.borrow(index).pub_key; + let public_key=slice_vector(&signer_pubkey,1,signer_pubkey.length()-1); + if(public_key == pubkey){ + return ((index+1) as u8) + }; + index=index+1; + }; + 0u8 + + + } + + public fun verify_pubkey(key:&vector,data:&vector,signature:&vector):bool{ + let flag=*key.borrow(0); + let public_key=slice_vector(key,1,key.length()-1); + + let verify= if(flag==FlagED25519){ + verify_ed25519(&public_key,data,signature) + + }else if(flag==FlagSecp256k1){ + verify_secp256k1(&public_key,data,signature) + }else if (flag==FlagSecp256r1){ + verify_secp256r1(&public_key,data,signature) + }else { + return false + }; + verify + + } + + fun verify_secp256k1(public_key:&vector,data:&vector,raw_signature:&vector):bool{ + let (sig,pub,_scheme)= split_signature(raw_signature); + assert!(*public_key==pub); + let intent_msg= get_intent_message(data); + let digest= hash::blake2b256(&intent_msg); + secp256k1_verify(&sig,&pub,&digest,SHA256) + + } + + fun verify_secp256r1(public_key:&vector,data:&vector,raw_signature:&vector):bool{ + let (sig,pub,_scheme)= split_signature(raw_signature); + assert!(*public_key==pub); + let intent_msg= get_intent_message(data); + let digest= hash::blake2b256(&intent_msg); + secp256r1_verify(&sig,&pub,&digest,SHA256) + + } + + fun verify_ed25519(public_key:&vector,data:&vector,raw_signature:&vector):bool{ + let (sig,pub,_scheme)= split_signature(raw_signature); + assert!(*public_key==pub); + let intent_msg= get_intent_message(data); + let digest= hash::blake2b256(&intent_msg); + ed25519_verify(&sig,&pub,&digest) + + } + + fun get_intent_message(msg:&vector):vector{ + let mut intent_message:vector =vector::empty(); + intent_message.push_back(0x00); + intent_message.push_back(0x00); + intent_message.push_back(0x00); + intent_message.append(*msg); + intent_message + + } + + fun split_signature(raw_signature:&vector):(vector,vector,u8){ + let scheme=*raw_signature.borrow(0); + let length= if (scheme==FlagED25519){32}else{33}; + let signature=slice_vector(raw_signature,1,64); + let pubkey=slice_vector(raw_signature,raw_signature.length()-length,length); + return (signature,pubkey,scheme) + } + + fun get_pubkey(wallet:&MultisigWallet,caller:address):(u64,vector){ + let mut i=0; + while(i , start: u64, length: u64): vector { + let mut result = vector::empty(); + let mut i = 0; + while (i < length) { + let value = *vector::borrow(vec, start + i); + vector::push_back(&mut result, value); + i = i + 1; + }; + result + } + + #[test_only] use sui::test_scenario::{Self,Scenario}; + #[test_only] + public fun init_state(admin:address,mut scenario:Scenario):Scenario{ + init(scenario.ctx()); + scenario.next_tx(admin); + scenario + } + + } #[test_only] module multisig::tests { use sui::hash::{Self}; - use multisig::multisig::{create_multisig_address}; + use multisig::multisig::{create_multisig_address,verify_pubkey,Signer,new_signer,create_multi_signature,serialize_multisig}; + + #[test] fun test_public__key_address(){ let pubkey = x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; @@ -182,12 +452,14 @@ module multisig::tests { public_keys.push_back(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); public_keys.push_back(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"); public_keys.push_back(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + let mut weights:vector = vector::empty(); weights.push_back(1u8); weights.push_back(1u8); weights.push_back(1u8); weights.push_back(1u8); weights.push_back(1u8); + let threshold:u16=3u16; let sui_addr= create_multisig_address(public_keys,weights,threshold); @@ -199,6 +471,120 @@ module multisig::tests { } + #[test] + fun test_verify_pubkey_with_secp256k1(){ + let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + let public_key=x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + let raw_signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); + assert!(verify==true); + + } + + #[test] + fun test_verify_pubkey_with_secp256r1(){ + let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + let public_key=x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"; + let raw_signature=x"0222194e51b82f0a13293c4ca9db40ef1a8c4115ace0047466627ece5c0e229766479486a6423f8332b13d8a13f2baba5d72f86c4a5d55c5b8c2c134f2a367299e023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"; + let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); + assert!(verify==true); + + } + + #[test] + fun test_verify_pubkey_with_ed25519(){ + let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + let public_key=x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"; + let raw_signature=x"00339e5f6df9aeac4e902767679407b1a6ae0e14db0036c4b19d8b45825036099299bb1948ef907b42df3a250b24395d49d1ff24e75748fadd32892934b22aa70e016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"; + let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); + assert!(verify==true); + + } + + #[test] + fun test_verify_pubkey(){ + let data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + + + let mut public_keys:vector> = vector::empty(); + public_keys.push_back(x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + public_keys.push_back(x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"); + public_keys.push_back(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + public_keys.push_back(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"); + public_keys.push_back(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + + let mut signatures:vector> = vector::empty(); + signatures.push_back(x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + signatures.push_back(x"00339e5f6df9aeac4e902767679407b1a6ae0e14db0036c4b19d8b45825036099299bb1948ef907b42df3a250b24395d49d1ff24e75748fadd32892934b22aa70e016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"); + signatures.push_back(x"019d51371b1fe243fc6bf92b0a6b58feb5cc7bfc0a90ee8900d2f5db8c22f7122f564dda95aff1ea5da752226686ff89e5c7c7e175d752bdc95d42c2bcf71cf160034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + signatures.push_back(x"0222194e51b82f0a13293c4ca9db40ef1a8c4115ace0047466627ece5c0e229766479486a6423f8332b13d8a13f2baba5d72f86c4a5d55c5b8c2c134f2a367299e023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"); + signatures.push_back(x"016a21ed6ace0e5c28dd6396dc004a8a9321f9a4c446f037275cdbaad81797c544570cd9e470d4f46d11eb512d8b8a2b6f00c16356e9f2505ead5bafd492387e4303b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + let mut i=0; + while( i < public_keys.length()){ + std::debug::print(public_keys.borrow(i)); + let signature=signatures.borrow(i); + let verify=verify_pubkey(public_keys.borrow(i),&data,signature); + i=i+1; + assert!(verify==true); + }; + + } + + #[test] + fun test_create_multisignature(){ + + let mut signers:vector =vector::empty(); + signers.push_back(new_signer(x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814",1)); + signers.push_back(new_signer(x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481",1)); + signers.push_back(new_signer(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804",1)); + signers.push_back(new_signer(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e",1)); + signers.push_back(new_signer(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598",1)); + let mut raw_signatures:vector> = vector::empty(); + raw_signatures.push_back(x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + raw_signatures.push_back(x"00339e5f6df9aeac4e902767679407b1a6ae0e14db0036c4b19d8b45825036099299bb1948ef907b42df3a250b24395d49d1ff24e75748fadd32892934b22aa70e016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"); + raw_signatures.push_back(x"019d51371b1fe243fc6bf92b0a6b58feb5cc7bfc0a90ee8900d2f5db8c22f7122f564dda95aff1ea5da752226686ff89e5c7c7e175d752bdc95d42c2bcf71cf160034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + + let multisignature= create_multi_signature(&raw_signatures,&signers,3_u16); + + std::debug::print(&serialize_multisig(&multisignature)); + + let expected=x"03030196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc7600339e5f6df9aeac4e902767679407b1a6ae0e14db0036c4b19d8b45825036099299bb1948ef907b42df3a250b24395d49d1ff24e75748fadd32892934b22aa70e019d51371b1fe243fc6bf92b0a6b58feb5cc7bfc0a90ee8900d2f5db8c22f7122f564dda95aff1ea5da752226686ff89e5c7c7e175d752bdc95d42c2bcf71cf16007000501033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba907261268140100016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f4810101034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d638040102023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e010103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598010300"; + assert!(expected==serialize_multisig(&multisignature)); + + + + } + + #[test] + fun test_create_multisignature_2(){ + + let mut signers:vector =vector::empty(); + signers.push_back(new_signer(x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814",1)); + signers.push_back(new_signer(x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481",1)); + signers.push_back(new_signer(x"01034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804",1)); + signers.push_back(new_signer(x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e",1)); + signers.push_back(new_signer(x"0103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598",1)); + let mut raw_signatures:vector> = vector::empty(); + raw_signatures.push_back(x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"); + raw_signatures.push_back(x"019d51371b1fe243fc6bf92b0a6b58feb5cc7bfc0a90ee8900d2f5db8c22f7122f564dda95aff1ea5da752226686ff89e5c7c7e175d752bdc95d42c2bcf71cf160034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d63804"); + raw_signatures.push_back(x"016a21ed6ace0e5c28dd6396dc004a8a9321f9a4c446f037275cdbaad81797c544570cd9e470d4f46d11eb512d8b8a2b6f00c16356e9f2505ead5bafd492387e4303b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598"); + + let multisignature= create_multi_signature(&raw_signatures,&signers,3_u16); + + std::debug::print(&serialize_multisig(&multisignature)); + + let expected=x"03030196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76019d51371b1fe243fc6bf92b0a6b58feb5cc7bfc0a90ee8900d2f5db8c22f7122f564dda95aff1ea5da752226686ff89e5c7c7e175d752bdc95d42c2bcf71cf160016a21ed6ace0e5c28dd6396dc004a8a9321f9a4c446f037275cdbaad81797c544570cd9e470d4f46d11eb512d8b8a2b6f00c16356e9f2505ead5bafd492387e4315000501033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba907261268140100016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f4810101034195b5a61eeebee6fd2b959ef6e23f7393b2b0717b6458eebe6ff72778d638040102023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e010103b13036d4a4adf7b9c36c5cd165613c82734e7541e3a47864fb5b8727e7920598010300"; + assert!(expected==serialize_multisig(&multisignature)); + + + + } + + + + + + diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move index 94a5355c..1d67a601 100644 --- a/contracts/sui/multisig/tests/multisig_tests.move +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -3,17 +3,136 @@ module multisig::multisig_tests { // uncomment this line to import the module use multisig::multisig; + use sui::test_scenario::{Self, Scenario}; + use multisig::multisig::AdminCap; + use multisig::multisig::Storage; + use std::string::{Self,String}; const ENotImplemented: u64 = 0; - #[test] - fun test_create_multisig() { - // let pub_keys=[]; + #[test_only] + fun setup_test(admin:address):Scenario { + let mut scenario = test_scenario::begin(admin); + scenario.next_tx(admin); + scenario=multisig::init_state(admin,scenario); + scenario + } + + #[test_only] + fun get_test_pub_keys():vector{ + let mut pubkeys:vector =vector::empty(); + pubkeys.push_back(string::utf8(b"AQM6YkAASHEsBpZFbeiCwm0Rmj3y/jFsWrFzi6kHJhJoFA==")); + pubkeys.push_back(string::utf8(b"AAFuArenKCbZUXiXkfPAU/kq+bwosnqOLGD8R0ylNvSB")); + pubkeys.push_back(string::utf8(b"AQNBlbWmHu6+5v0rlZ724j9zk7KwcXtkWO6+b/cneNY4BA==")); + pubkeys.push_back(string::utf8(b"AgI+y4/2z460dI72+wYutS+GKwk+s6QtYp2J5D2WRRCPng==")); + pubkeys.push_back(string::utf8(b"AQOxMDbUpK33ucNsXNFlYTyCc051QeOkeGT7W4cn55IFmA==")); + pubkeys + } - #[test, expected_failure(abort_code = ::multisig::multisig_tests::ENotImplemented)] - fun test_multisig_fail() { - abort ENotImplemented + fun get_test_weights():vector{ + let mut weights: vector = vector::empty(); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights } + + #[test] + fun test_register_wallet() { + let admin = @0xBABE; + + let mut _scenario = setup_test(admin); + let scenario = & _scenario; + + { + let adminCap = test_scenario::take_from_sender(scenario); + let mut storage = test_scenario::take_shared(scenario); + // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 + let pubkeys:vector =get_test_pub_keys(); + let weights: vector = get_test_weights(); + let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + + + multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); + assert!(storage.get_wallets().size()>0); + let wallet=storage.get_wallets().get(&expected); + assert!(wallet.multisig_address()==expected); + scenario.return_to_sender(adminCap); + test_scenario::return_shared( storage); + }; + _scenario.end(); + +} + +#[test] +fun test_create_proposal() { + + let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; + + let mut scenario = setup_test(admin); + { + let adminCap = test_scenario::take_from_sender(&scenario); + let mut storage = test_scenario::take_shared(&scenario); + // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 + let pubkeys:vector =get_test_pub_keys(); + let weights: vector = get_test_weights(); + + multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); + + + scenario.return_to_sender(adminCap); + test_scenario::return_shared( storage); + + }; + test_scenario::next_tx(&mut scenario, admin); + { + let adminCap = test_scenario::take_from_sender(&scenario); + let mut storage = test_scenario::take_shared(&scenario); + let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + + multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data,expected,scenario.ctx()); + assert!(storage.get_proposals().length()>0); + scenario.return_to_sender(adminCap); + test_scenario::return_shared( storage); + + }; + + scenario.end(); + +} + +#[test] +fun test_approve_proposal() { + + let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; + + let mut scenario = setup_test(admin); + { + let adminCap = test_scenario::take_from_sender(&scenario); + let mut storage = test_scenario::take_shared(&scenario); + // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 + let pubkeys:vector =get_test_pub_keys(); + let weights: vector = get_test_weights(); + let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + + multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); + test_scenario::next_tx(&mut scenario, admin); + multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data,expected,scenario.ctx()); + test_scenario::next_tx(&mut scenario, admin); + assert!(storage.get_proposals().length()>0); + let signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + multisig::approve_proposal(&mut storage,1,signature,scenario.ctx()); + scenario.return_to_sender(adminCap); + test_scenario::return_shared( storage); + + }; + scenario.end(); + +} } From 74b28b74ba9d020f11d25a704b072a6146d4fe15 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Fri, 23 Aug 2024 15:46:58 +0545 Subject: [PATCH 03/75] fix: add support for digest --- contracts/sui/multisig/sources/multisig.move | 102 ++++++------------ .../sui/multisig/tests/multisig_tests.move | 9 +- 2 files changed, 39 insertions(+), 72 deletions(-) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index 2aeaecce..09bec29a 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -70,6 +70,7 @@ module multisig::multisig { title:String, multisig_address:address, tx_data:vector, + is_digest:bool, } public struct Vote has store,drop{ @@ -164,15 +165,18 @@ module multisig::multisig { storage.wallet_proposals.add(multisig_addr, vector::empty()); } - entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes:vector,multisig_address:address,ctx:&TxContext){ + entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes_64:String,multisig_address:address,ctx:&TxContext){ + let tx_bytes=base64::decode(&tx_bytes_64); let wallet=storage.wallets.get(&multisig_address); assert!(only_member(wallet,ctx.sender())==true); + let is_digest=tx_bytes.length()==32; let proposal_id=get_proposal_id(storage); let proposal= Proposal{ id:proposal_id, title:title, multisig_address:multisig_address, tx_data:tx_bytes, + is_digest, }; storage.proposals.add(proposal_id,proposal); @@ -180,20 +184,20 @@ module multisig::multisig { } - entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,signature:vector,ctx:&TxContext){ + entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,raw_signature:vector,ctx:&TxContext){ let proposal = storage.proposals.borrow(proposal_id); let wallet= storage.wallets.get(&proposal.multisig_address); assert!(only_member(wallet,ctx.sender())==true); let (index,pubkey)=get_pubkey(wallet,ctx.sender()); assert!(index!=0); - assert!(verify_pubkey(&pubkey,&proposal.tx_data,&signature)==true); + assert!(verify_pubkey(&pubkey,&proposal.tx_data,&raw_signature,proposal.is_digest)==true); let vote_key=VoteKey{ proposal_id:proposal_id, sui_address:ctx.sender() }; assert!(storage.votes.contains(vote_key)==false); storage.votes.add(vote_key, Vote{ - signature:signature, + signature:raw_signature, voter:ctx.sender() }); @@ -226,7 +230,12 @@ module multisig::multisig { let mut command=vector::empty(); command.append(b"sui client execute-signed-tx --tx-bytes "); let tx_data_64= base64::encode(&proposal.tx_data); - command.append(*tx_data_64.as_bytes()); + if(proposal.is_digest){ + command.append(b"${ORIGINAL_TX_BYTES}"); + }else { + command.append(*tx_data_64.as_bytes()); + }; + command.append(b" --signatures "); command.append(*multisig_serialized_64.as_bytes()); @@ -335,17 +344,29 @@ module multisig::multisig { } - public fun verify_pubkey(key:&vector,data:&vector,signature:&vector):bool{ + public fun verify_pubkey(key:&vector,data:&vector,raw_signature:&vector,is_digest:bool):bool{ let flag=*key.borrow(0); let public_key=slice_vector(key,1,key.length()-1); - + let (signature,pub,_scheme)= split_signature(raw_signature); + assert!(public_key==pub); + let digest= if(is_digest==true){ + data + }else { + let intent_msg= get_intent_message(data); + let digest= hash::blake2b256(&intent_msg); + &digest + }; let verify= if(flag==FlagED25519){ - verify_ed25519(&public_key,data,signature) + + ed25519_verify(&signature,&public_key,digest) }else if(flag==FlagSecp256k1){ - verify_secp256k1(&public_key,data,signature) + + secp256k1_verify(&signature,&public_key,digest,SHA256) + }else if (flag==FlagSecp256r1){ - verify_secp256r1(&public_key,data,signature) + + secp256r1_verify(&signature,&public_key,digest,SHA256) }else { return false }; @@ -353,33 +374,7 @@ module multisig::multisig { } - fun verify_secp256k1(public_key:&vector,data:&vector,raw_signature:&vector):bool{ - let (sig,pub,_scheme)= split_signature(raw_signature); - assert!(*public_key==pub); - let intent_msg= get_intent_message(data); - let digest= hash::blake2b256(&intent_msg); - secp256k1_verify(&sig,&pub,&digest,SHA256) - - } - - fun verify_secp256r1(public_key:&vector,data:&vector,raw_signature:&vector):bool{ - let (sig,pub,_scheme)= split_signature(raw_signature); - assert!(*public_key==pub); - let intent_msg= get_intent_message(data); - let digest= hash::blake2b256(&intent_msg); - secp256r1_verify(&sig,&pub,&digest,SHA256) - - } - - fun verify_ed25519(public_key:&vector,data:&vector,raw_signature:&vector):bool{ - let (sig,pub,_scheme)= split_signature(raw_signature); - assert!(*public_key==pub); - let intent_msg= get_intent_message(data); - let digest= hash::blake2b256(&intent_msg); - ed25519_verify(&sig,&pub,&digest) - - } - + fun get_intent_message(msg:&vector):vector{ let mut intent_message:vector =vector::empty(); intent_message.push_back(0x00); @@ -469,36 +464,6 @@ module multisig::tests { - } - - #[test] - fun test_verify_pubkey_with_secp256k1(){ - let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - let public_key=x"01033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; - let raw_signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; - let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); - assert!(verify==true); - - } - - #[test] - fun test_verify_pubkey_with_secp256r1(){ - let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - let public_key=x"02023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"; - let raw_signature=x"0222194e51b82f0a13293c4ca9db40ef1a8c4115ace0047466627ece5c0e229766479486a6423f8332b13d8a13f2baba5d72f86c4a5d55c5b8c2c134f2a367299e023ecb8ff6cf8eb4748ef6fb062eb52f862b093eb3a42d629d89e43d9645108f9e"; - let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); - assert!(verify==true); - - } - - #[test] - fun test_verify_pubkey_with_ed25519(){ - let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - let public_key=x"00016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"; - let raw_signature=x"00339e5f6df9aeac4e902767679407b1a6ae0e14db0036c4b19d8b45825036099299bb1948ef907b42df3a250b24395d49d1ff24e75748fadd32892934b22aa70e016e02b7a72826d951789791f3c053f92af9bc28b27a8e2c60fc474ca536f481"; - let verify=verify_pubkey(&public_key,&tx_data,&raw_signature); - assert!(verify==true); - } #[test] @@ -523,8 +488,9 @@ module multisig::tests { while( i < public_keys.length()){ std::debug::print(public_keys.borrow(i)); let signature=signatures.borrow(i); - let verify=verify_pubkey(public_keys.borrow(i),&data,signature); + let verify=verify_pubkey(public_keys.borrow(i),&data,signature,false); i=i+1; + assert!(verify==true); }; diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move index 1d67a601..1393f4e4 100644 --- a/contracts/sui/multisig/tests/multisig_tests.move +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -7,6 +7,7 @@ module multisig::multisig_tests { use multisig::multisig::AdminCap; use multisig::multisig::Storage; use std::string::{Self,String}; + use multisig::base64::{Self}; const ENotImplemented: u64 = 0; @@ -93,8 +94,8 @@ fun test_create_proposal() { let mut storage = test_scenario::take_shared(&scenario); let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - - multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data,expected,scenario.ctx()); + let tx_data_64=base64::encode(&tx_data); + multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data_64,expected,scenario.ctx()); assert!(storage.get_proposals().length()>0); scenario.return_to_sender(adminCap); test_scenario::return_shared( storage); @@ -119,10 +120,10 @@ fun test_approve_proposal() { let weights: vector = get_test_weights(); let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - + let tx_data_64=base64::encode(&tx_data); multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); test_scenario::next_tx(&mut scenario, admin); - multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data,expected,scenario.ctx()); + multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data_64,expected,scenario.ctx()); test_scenario::next_tx(&mut scenario, admin); assert!(storage.get_proposals().length()>0); let signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; From c2d2514a56228186329b2b1e2a72646fa8702eef Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Mon, 26 Aug 2024 10:05:16 +0545 Subject: [PATCH 04/75] fix: accept base64 in approve --- contracts/sui/multisig/sources/multisig.move | 3 ++- contracts/sui/multisig/tests/multisig_tests.move | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index 09bec29a..0ac6bf2a 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -184,7 +184,8 @@ module multisig::multisig { } - entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,raw_signature:vector,ctx:&TxContext){ + entry fun approve_proposal(storage:&mut Storage,proposal_id:u64,raw_signature_64:String,ctx:&TxContext){ + let raw_signature=base64::decode(&raw_signature_64); let proposal = storage.proposals.borrow(proposal_id); let wallet= storage.wallets.get(&proposal.multisig_address); assert!(only_member(wallet,ctx.sender())==true); diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move index 1393f4e4..fc56602d 100644 --- a/contracts/sui/multisig/tests/multisig_tests.move +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -127,7 +127,8 @@ fun test_approve_proposal() { test_scenario::next_tx(&mut scenario, admin); assert!(storage.get_proposals().length()>0); let signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; - multisig::approve_proposal(&mut storage,1,signature,scenario.ctx()); + let signature_64=base64::encode(&signature); + multisig::approve_proposal(&mut storage,1,signature_64,scenario.ctx()); scenario.return_to_sender(adminCap); test_scenario::return_shared( storage); From e0c320b496538436416afbef3155b3a7e1ad23c5 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Mon, 26 Aug 2024 13:42:52 +0545 Subject: [PATCH 05/75] fix: add execute event --- contracts/sui/multisig/sources/multisig.move | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index 0ac6bf2a..c012090a 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -14,6 +14,7 @@ module multisig::multisig { use sui::{ed25519::ed25519_verify}; use sui::{ecdsa_k1::secp256k1_verify}; use sui::{ecdsa_r1::secp256r1_verify}; + /** signature schemes*/ const FlagED25519 :u8= 0x00; @@ -25,6 +26,12 @@ module multisig::multisig { const KECCAK256: u8 = 0x00; const SHA256: u8 = 0x01; + public struct Executed has copy, drop{ + proposal_id:u64, + command:String, + + } + public struct Signer has store,drop{ pub_key:vector, @@ -206,6 +213,11 @@ module multisig::multisig { } + entry fun execute_event(storage:&Storage,proposal_id:u64){ + let command= get_execute_command(storage,proposal_id); + event::emit(Executed {proposal_id:proposal_id,command:command}); + } + public fun get_execute_command(storage:&Storage,proposal_id:u64):String{ let proposal=storage.proposals.borrow(proposal_id); let wallet=storage.wallets.get(&proposal.multisig_address); From 72146111f5d6963dc2652c43e9c73c36a3841caf Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Wed, 28 Aug 2024 14:24:34 +0545 Subject: [PATCH 06/75] fix: cleanup --- contracts/sui/multisig/sources/base64.move | 154 ++++++++--------- contracts/sui/multisig/sources/multisig.move | 9 +- .../sui/multisig/tests/multisig_tests.move | 160 +++++++++--------- 3 files changed, 163 insertions(+), 160 deletions(-) diff --git a/contracts/sui/multisig/sources/base64.move b/contracts/sui/multisig/sources/base64.move index 551fc0c2..2ef0330b 100644 --- a/contracts/sui/multisig/sources/base64.move +++ b/contracts/sui/multisig/sources/base64.move @@ -1,5 +1,5 @@ module multisig::base64 { - use std::vector::{Self}; + use std::vector::{Self}; use std::string::{Self,String}; use sui::vec_map::{Self, VecMap}; @@ -7,94 +7,95 @@ module multisig::base64 { const PADDING_CHAR: vector = b"="; -public fun encode(input:&vector):String { - - let mut output:vector = vector::empty(); - let mut i=0; - while (i ):String { + + let mut output:vector = vector::empty(); + let mut i=0; + while (i > 18) & 0x3F) as u64)); + output.push_back(*BASE64_CHARS.borrow(((triple >> 12) & 0x3F) as u64)); + + if (i + 1 < input.length()) { + output.push_back(*BASE64_CHARS.borrow(((triple >> 6) & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + if (i + 2 < input.length()) { + output.push_back(*BASE64_CHARS.borrow((triple & 0x3F) as u64)); + } else { + output.push_back(*PADDING_CHAR.borrow(0)); + }; + + i =i+ 3; }; + string::utf8(output) + } - let b3:u8= if((i+2)> 18) & 0x3F) as u64)); - output.push_back(*BASE64_CHARS.borrow(((triple >> 12) & 0x3F) as u64)); - - if (i + 1 < input.length()) { - output.push_back(*BASE64_CHARS.borrow(((triple >> 6) & 0x3F) as u64)); - } else { - output.push_back(*PADDING_CHAR.borrow(0)); - }; - - if (i + 2 < input.length()) { - output.push_back(*BASE64_CHARS.borrow((triple & 0x3F) as u64)); - } else { - output.push_back(*PADDING_CHAR.borrow(0)); - }; - - i =i+ 3; - }; - string::utf8(output) -} - -public fun decode(input:&String):vector{ - let char_index=get_char_map(); - let mut output = vector::empty(); - let input_bytes = input.as_bytes(); - let mut i = 0; - while( i < input_bytes.length()){ - let b1 = *char_index.get(input_bytes.borrow(i)); - let b2 = *char_index.get(input_bytes.borrow(i + 1)); - let b3 = if (i + 2 < input_bytes.length()) { - let key=input_bytes.borrow(i+2); - let val:u32 = if (char_index.contains(key)) { - *char_index.get(key) - } else { - 64 - }; - val - } else { - 64 - }; - let b4 = if (i + 3 < input_bytes.length()) { - let key=input_bytes.borrow(i+3); - let val:u32= if (char_index.contains(key)) { - *char_index.get(key) + public fun decode(input:&String):vector{ + let char_index=get_char_map(); + let mut output = vector::empty(); + let input_bytes = input.as_bytes(); + let mut i = 0; + while( i < input_bytes.length()){ + let b1 = *char_index.get(input_bytes.borrow(i)); + let b2 = *char_index.get(input_bytes.borrow(i + 1)); + let b3 = if (i + 2 < input_bytes.length()) { + let key=input_bytes.borrow(i+2); + let val:u32 = if (char_index.contains(key)) { + *char_index.get(key) } else { 64 }; val - } else { + } else { + 64 + }; + + let b4 = if (i + 3 < input_bytes.length()) { + let key=input_bytes.borrow(i+3); + let val:u32= if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + 64 + }; - 64 - }; + let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; - let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; + output.push_back(((triple >> 16) & 0xFF) as u8); - output.push_back(((triple >> 16) & 0xFF) as u8); + if (b3 != 64) { + output.push_back(((triple >> 8) & 0xFF) as u8); + }; - if (b3 != 64) { - output.push_back(((triple >> 8) & 0xFF) as u8); - }; - if (b4 != 64) { - output.push_back((triple & 0xFF) as u8); - }; + if (b4 != 64) { + output.push_back((triple & 0xFF) as u8); + }; - i = i+4; + i = i+4; - }; - output + }; + output -} + } @@ -124,7 +125,6 @@ module multisig::base64_tests { let decoded = base64::decode(&encoded); std::debug::print(&encoded); assert!(decoded==input); - } diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index c012090a..57459bd6 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -208,9 +208,6 @@ module multisig::multisig { signature:raw_signature, voter:ctx.sender() }); - - - } entry fun execute_event(storage:&Storage,proposal_id:u64){ @@ -240,9 +237,11 @@ module multisig::multisig { let multisig= create_multi_signature(&signatures,&wallet.signers,wallet.threshold); let multisig_serialized_64= base64::encode(&serialize_multisig(&multisig)); + let mut command=vector::empty(); command.append(b"sui client execute-signed-tx --tx-bytes "); let tx_data_64= base64::encode(&proposal.tx_data); + if(proposal.is_digest){ command.append(b"${ORIGINAL_TX_BYTES}"); }else { @@ -251,11 +250,7 @@ module multisig::multisig { command.append(b" --signatures "); command.append(*multisig_serialized_64.as_bytes()); - string::utf8(command) - - - } fun only_member(wallet:&MultisigWallet,caller:address):bool{ diff --git a/contracts/sui/multisig/tests/multisig_tests.move b/contracts/sui/multisig/tests/multisig_tests.move index fc56602d..94bfc0eb 100644 --- a/contracts/sui/multisig/tests/multisig_tests.move +++ b/contracts/sui/multisig/tests/multisig_tests.move @@ -1,140 +1,148 @@ - #[test_only] module multisig::multisig_tests { // uncomment this line to import the module use multisig::multisig; - use sui::test_scenario::{Self, Scenario}; - use multisig::multisig::AdminCap; - use multisig::multisig::Storage; - use std::string::{Self,String}; - use multisig::base64::{Self}; + use sui::test_scenario::{Self, Scenario}; + use multisig::multisig::AdminCap; + use multisig::multisig::Storage; + use std::string::{Self, String}; + use multisig::base64::{ Self }; const ENotImplemented: u64 = 0; #[test_only] - fun setup_test(admin:address):Scenario { + fun setup_test(admin: address): Scenario { let mut scenario = test_scenario::begin(admin); scenario.next_tx(admin); - scenario=multisig::init_state(admin,scenario); + scenario = multisig::init_state(admin, scenario); scenario } - #[test_only] - fun get_test_pub_keys():vector{ - let mut pubkeys:vector =vector::empty(); - pubkeys.push_back(string::utf8(b"AQM6YkAASHEsBpZFbeiCwm0Rmj3y/jFsWrFzi6kHJhJoFA==")); - pubkeys.push_back(string::utf8(b"AAFuArenKCbZUXiXkfPAU/kq+bwosnqOLGD8R0ylNvSB")); - pubkeys.push_back(string::utf8(b"AQNBlbWmHu6+5v0rlZ724j9zk7KwcXtkWO6+b/cneNY4BA==")); - pubkeys.push_back(string::utf8(b"AgI+y4/2z460dI72+wYutS+GKwk+s6QtYp2J5D2WRRCPng==")); - pubkeys.push_back(string::utf8(b"AQOxMDbUpK33ucNsXNFlYTyCc051QeOkeGT7W4cn55IFmA==")); - pubkeys + #[test_only] + fun get_test_pub_keys(): vector { + let mut pubkeys: vector = vector::empty(); + pubkeys.push_back(string::utf8(b"AQM6YkAASHEsBpZFbeiCwm0Rmj3y/jFsWrFzi6kHJhJoFA==")); + pubkeys.push_back(string::utf8(b"AAFuArenKCbZUXiXkfPAU/kq+bwosnqOLGD8R0ylNvSB")); + pubkeys.push_back(string::utf8(b"AQNBlbWmHu6+5v0rlZ724j9zk7KwcXtkWO6+b/cneNY4BA==")); + pubkeys.push_back(string::utf8(b"AgI+y4/2z460dI72+wYutS+GKwk+s6QtYp2J5D2WRRCPng==")); + pubkeys.push_back(string::utf8(b"AQOxMDbUpK33ucNsXNFlYTyCc051QeOkeGT7W4cn55IFmA==")); + pubkeys } - fun get_test_weights():vector{ + fun get_test_weights(): vector { let mut weights: vector = vector::empty(); - weights.push_back(1); - weights.push_back(1); - weights.push_back(1); - weights.push_back(1); - weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); + weights.push_back(1); weights } #[test] - fun test_register_wallet() { - let admin = @0xBABE; + fun test_register_wallet() { + let admin = @0xBABE; let mut _scenario = setup_test(admin); - let scenario = & _scenario; + let scenario = &_scenario; { let adminCap = test_scenario::take_from_sender(scenario); let mut storage = test_scenario::take_shared(scenario); // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 - let pubkeys:vector =get_test_pub_keys(); - let weights: vector = get_test_weights(); - let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; - - - multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); - assert!(storage.get_wallets().size()>0); - let wallet=storage.get_wallets().get(&expected); - assert!(wallet.multisig_address()==expected); + let pubkeys: vector = get_test_pub_keys(); + let weights: vector = get_test_weights(); + let expected = @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + + multisig::register_wallet(&mut storage, &adminCap, pubkeys, weights, 3); + assert!(storage.get_wallets().size() > 0); + let wallet = storage.get_wallets().get(&expected); + assert!(wallet.multisig_address() == expected); scenario.return_to_sender(adminCap); - test_scenario::return_shared( storage); + test_scenario::return_shared(storage); }; _scenario.end(); - -} -#[test] -fun test_create_proposal() { + } + + #[test] + fun test_create_proposal() { - let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; + let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; let mut scenario = setup_test(admin); { let adminCap = test_scenario::take_from_sender(&scenario); let mut storage = test_scenario::take_shared(&scenario); // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 - let pubkeys:vector =get_test_pub_keys(); - let weights: vector = get_test_weights(); - - multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); - - + let pubkeys: vector = get_test_pub_keys(); + let weights: vector = get_test_weights(); + + multisig::register_wallet(&mut storage, &adminCap, pubkeys, weights, 3); + scenario.return_to_sender(adminCap); - test_scenario::return_shared( storage); - + test_scenario::return_shared(storage); + }; test_scenario::next_tx(&mut scenario, admin); { let adminCap = test_scenario::take_from_sender(&scenario); let mut storage = test_scenario::take_shared(&scenario); - let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; - let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - let tx_data_64=base64::encode(&tx_data); - multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data_64,expected,scenario.ctx()); - assert!(storage.get_proposals().length()>0); + let expected = @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + let tx_data = x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + let tx_data_64 = base64::encode(&tx_data); + multisig::create_proposal( + &mut storage, + string::utf8(b"test proposal"), + tx_data_64, + expected, + scenario.ctx() + ); + assert!(storage.get_proposals().length() > 0); scenario.return_to_sender(adminCap); - test_scenario::return_shared( storage); + test_scenario::return_shared(storage); }; scenario.end(); - -} -#[test] -fun test_approve_proposal() { + } + + #[test] + fun test_approve_proposal() { - let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; + let admin = @0x29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589; let mut scenario = setup_test(admin); { let adminCap = test_scenario::take_from_sender(&scenario); let mut storage = test_scenario::take_shared(&scenario); // storage:&mut Storage,_admin:&AdminCap, pub_keys:vector,weights:vector,threshold:u16 - let pubkeys:vector =get_test_pub_keys(); - let weights: vector = get_test_weights(); - let expected= @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; - let tx_data=x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; - let tx_data_64=base64::encode(&tx_data); - multisig::register_wallet(&mut storage,&adminCap,pubkeys,weights,3); + let pubkeys: vector = get_test_pub_keys(); + let weights: vector = get_test_weights(); + let expected = @0x34f45f30d3af0393474ce42fc7a1de48aa8a9ddf03383062d8fcd1842d627a2f; + let tx_data = x"00000200203fab45fb191ca013a74ccfc3b7d5ed27a3ef6dce79adc4d1e39555f01a361bf801001b61730f57e4d64241cecb40ac259c58ced75018bd231acdbe463ddb9ac99176480000000000000020e12acf2025db82d420e8696d3645dfd25c6542382883f6e3762cc655791a007e01010101010001000029a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c20458901c2a6df77449ebce38397d97785f52c2341abafc21c47523e9cfb71d141d5df614800000000000000209e4e3d3f48881797ecc3690b237e0353e16a8be1cd79ac75210657229942e12b29a0918bee7a7e37d1a7d0613efc3f4455883ea217046f7db91d53e69c204589e80300000000000078be2d000000000000"; + let tx_data_64 = base64::encode(&tx_data); + multisig::register_wallet(&mut storage, &adminCap, pubkeys, weights, 3); test_scenario::next_tx(&mut scenario, admin); - multisig::create_proposal(&mut storage,string::utf8(b"test proposal"),tx_data_64,expected,scenario.ctx()); + multisig::create_proposal( + &mut storage, + string::utf8(b"test proposal"), + tx_data_64, + expected, + scenario.ctx() + ); test_scenario::next_tx(&mut scenario, admin); - assert!(storage.get_proposals().length()>0); - let signature=x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; - let signature_64=base64::encode(&signature); - multisig::approve_proposal(&mut storage,1,signature_64,scenario.ctx()); + assert!(storage.get_proposals().length() > 0); + let signature = x"0196e3d1a05e3d9d900281da7a3719dada72b66fdcfd4147275634f3028d71dba02896bf6958bdc97d93462bd0aa7245a04f05caa3e7f09465d725fa79f91fcc76033a62400048712c0696456de882c26d119a3df2fe316c5ab1738ba90726126814"; + let signature_64 = base64::encode(&signature); + multisig::approve_proposal(&mut storage, 1, signature_64, scenario.ctx()); scenario.return_to_sender(adminCap); - test_scenario::return_shared( storage); - + test_scenario::return_shared(storage); + }; scenario.end(); - -} -} + } +} From d4b172bac75f7984cb7f152006049cb98085fc71 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Tue, 17 Sep 2024 18:56:49 +0545 Subject: [PATCH 07/75] feat: register packet in aggregator --- contracts/javascore/aggregator/build.gradle | 43 ++++++++++ .../relay/aggregator/RelayAggregator.java | 86 +++++++++++++++++++ contracts/javascore/settings.gradle | 3 +- 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 contracts/javascore/aggregator/build.gradle create mode 100644 contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle new file mode 100644 index 00000000..f8881dcc --- /dev/null +++ b/contracts/javascore/aggregator/build.gradle @@ -0,0 +1,43 @@ +version = '0.1.0' + +dependencies { + testImplementation 'foundation.icon:javaee-unittest:0.11.1' + testImplementation project(':test-lib') +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport +} + +optimizedJar { + mainClassName = 'relay.aggregator.RelayAggregator' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +deployJar { + endpoints { + lisbon { + uri = 'https://lisbon.net.solidwallet.io/api/v3' + nid = 0x2 + } + local { + uri = 'http://localhost:9082/api/v3' + nid = 0x3 + } + mainnet { + uri = 'https://ctz.solidwallet.io/api/v3' + nid = 0x1 + } + } + keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : '' + password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' + parameters { + arg('_admin', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") + arg('_relayers', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") + arg('_relayers', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fe") + } +} \ No newline at end of file diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java new file mode 100644 index 00000000..1364a573 --- /dev/null +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -0,0 +1,86 @@ +/* + * Copyright 2022 ICON Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package relay.aggregator; + +import score.Context; + +import java.math.BigInteger; + +import score.Address; +import score.VarDB; +import score.DictDB; +import score.BranchDB; + +import score.annotation.External; + +public class RelayAggregator { + protected final VarDB
admin = Context.newVarDB("admin", Address.class); + + protected final DictDB relayers = Context.newDictDB("relayers", Boolean.class); + + protected final BranchDB> packets = Context.newBranchDB("packets", byte[].class); + + // protected final BranchDB> signatures = Context.newBranchDB("signatures", String[].class); + + public RelayAggregator(Address _admin, Address[] _relayers) { + if (admin.get() == null) { + admin.set(_admin); + for (Address relayer : _relayers) { + relayers.set(relayer, true); + } + } + } + + /** + * Registers a new packet. + * + * @param nid the network ID + * @param sn the sequence number + * @param data the packet data + */ + @External + public void registerPacket(String nid, BigInteger sn, byte[] data) { + adminOnly(); + DictDB packetDict = packets.at(nid); + byte[] pkt = packetDict.get(sn); + Context.require(pkt == null, "Packet already exists."); + + packetDict.set(sn, data); + } + + + /** + * Sets the admin address. + * + * @param _admin the new admin address + */ + @External + public void setAdmin(Address _admin) { + adminOnly(); + admin.set(_admin); + } + + + /** + * Checks if the caller of the function is the admin. + * + * @return true if the caller is the admin, false otherwise + */ + private void adminOnly() { + Context.require(Context.getCaller().equals(admin.get()), "Unauthorized to call this method"); + } +} \ No newline at end of file diff --git a/contracts/javascore/settings.gradle b/contracts/javascore/settings.gradle index 6e6fdcdf..243072ad 100644 --- a/contracts/javascore/settings.gradle +++ b/contracts/javascore/settings.gradle @@ -3,7 +3,8 @@ include( 'test-lib', 'xcall', 'xcall-lib', - 'centralized-connection' + 'centralized-connection', + 'aggregator' ) include(':dapp-simple') From 46c202e51980b9f244b2ec88f171c0c112c27b04 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Tue, 17 Sep 2024 23:25:00 +0545 Subject: [PATCH 08/75] feat: add unit tests for aggregator --- .../relay/aggregator/RelayAggregator.java | 9 ++++ .../relay/aggregator/RelayAggregatorTest.java | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java 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 1364a573..81cfe28d 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -74,6 +74,15 @@ public void setAdmin(Address _admin) { admin.set(_admin); } + /** + * Retrieves the admin address. + * + * @return The admin address. + */ + @External(readonly = true) + public Address getAdmin() { + return admin.get(); + } /** * Checks if the caller of the function is the admin. diff --git a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java new file mode 100644 index 00000000..50dcb21f --- /dev/null +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -0,0 +1,52 @@ +package relay.aggregator; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import score.Address; +import score.UserRevertedException; + +import com.iconloop.score.test.Account; +import com.iconloop.score.test.Score; +import com.iconloop.score.test.ServiceManager; +import com.iconloop.score.test.TestBase; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class RelayAggregatorTest extends TestBase { + protected final ServiceManager sm = getServiceManager(); + + protected final Account adminAc = sm.createAccount(); + protected final Account relayerAcOne = sm.createAccount(); + protected final Account relayerAcTwo = sm.createAccount(); + + protected Score aggregator; + + @BeforeEach + void setup() throws Exception { + Address[] relayers = new Address[]{relayerAcOne.getAddress(), relayerAcTwo.getAddress()}; + aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); + } + + @Test + public void testSetAdmin() { + Account newAdminAc = sm.createAccount(); + aggregator.invoke(adminAc, "setAdmin", newAdminAc.getAddress()); + + Address result = (Address) aggregator.call("getAdmin"); + assertEquals(newAdminAc.getAddress(), result); + } + + @Test + public void testSetAdmin_unauthorized() { + Account normalAc = sm.createAccount(); + Account newAdminAc = sm.createAccount(); + + Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress()); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized to call this method", e.getMessage()); + } +} From 395acc78f01174a0f02d552dfd534f4e13b64d1b Mon Sep 17 00:00:00 2001 From: sherpalden Date: Mon, 23 Sep 2024 18:04:11 +0545 Subject: [PATCH 09/75] test: submitSignature --- contracts/javascore/aggregator/build.gradle | 5 + .../main/java/relay/aggregator/HexString.java | 26 ++++ .../relay/aggregator/RelayAggregator.java | 85 +++++++++-- .../relay/aggregator/RelayAggregatorTest.java | 140 +++++++++++++++++- contracts/javascore/keystore.json | 1 + 5 files changed, 239 insertions(+), 18 deletions(-) create mode 100644 contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java create mode 100644 contracts/javascore/keystore.json diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle index f8881dcc..0704d410 100644 --- a/contracts/javascore/aggregator/build.gradle +++ b/contracts/javascore/aggregator/build.gradle @@ -1,6 +1,11 @@ version = '0.1.0' +repositories { + mavenCentral() +} + dependencies { + implementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'foundation.icon:javaee-unittest:0.11.1' testImplementation project(':test-lib') } diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java new file mode 100644 index 00000000..6c12e466 --- /dev/null +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java @@ -0,0 +1,26 @@ +package relay.aggregator; + +public class HexString { + public static byte[] toBytes(String hex) { + int len = hex.length(); + byte[] data = new byte[len / 2]; + + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + + Character.digit(hex.charAt(i + 1), 16)); + } + return data; + } + + public static String fromBytes(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); // Append leading zero if needed + } + hexString.append(hex); + } + return hexString.toString(); + } +} 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 81cfe28d..a7a5e979 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -16,10 +16,10 @@ package relay.aggregator; -import score.Context; import java.math.BigInteger; +import score.Context; import score.Address; import score.VarDB; import score.DictDB; @@ -28,13 +28,13 @@ import score.annotation.External; public class RelayAggregator { - protected final VarDB
admin = Context.newVarDB("admin", Address.class); + private final VarDB
admin = Context.newVarDB("admin", Address.class); - protected final DictDB relayers = Context.newDictDB("relayers", Boolean.class); + private final DictDB relayers = Context.newDictDB("relayers", Boolean.class); - protected final BranchDB> packets = Context.newBranchDB("packets", byte[].class); + private final BranchDB> packets = Context.newBranchDB("packets", byte[].class); - // protected final BranchDB> signatures = Context.newBranchDB("signatures", String[].class); + private final BranchDB>> signatures = Context.newBranchDB("signatures", DictDB.class); public RelayAggregator(Address _admin, Address[] _relayers) { if (admin.get() == null) { @@ -48,20 +48,60 @@ public RelayAggregator(Address _admin, Address[] _relayers) { /** * Registers a new packet. * - * @param nid the network ID - * @param sn the sequence number - * @param data the packet data + * @param nid network ID + * @param sn sequence number + * @param data packet data */ @External public void registerPacket(String nid, BigInteger sn, byte[] data) { adminOnly(); - DictDB packetDict = packets.at(nid); + DictDB packetDict = getPackets(nid); byte[] pkt = packetDict.get(sn); - Context.require(pkt == null, "Packet already exists."); + Context.require(pkt == null, "Packet already exists"); packetDict.set(sn, data); } + /** + * Submits a signature for the registered packet. + * + * @param nid network ID + * @param sn sequence number + * @param signature packet signature + */ + @External + public void submitSignature(String nid, BigInteger sn, String signature) { + relayersOnly(); + + DictDB packetDict = getPackets(nid); + byte[] packetData = packetDict.get(sn); + Context.require(packetData != null, "Packet does not exist"); + + + DictDB addressSigns = getSignatures(nid, sn); + + Address caller = Context.getCaller(); + String sign = addressSigns.get(caller); + Context.require(sign == null, "Signature already exists."); + + addressSigns.set(caller, signature); + } + + /** + * Retrieves the signatures dictionary for the packet. + * + * @param nid network ID of the source chain + * @param sn sequence number of the source chain message + * @return map of relayer addresses to signatures + */ + protected DictDB getSignatures(String nid, BigInteger sn) { + BranchDB> snSigns = signatures.at(nid); + if (snSigns == null) { + snSigns = Context.newBranchDB(nid+"-signs", DictDB.class); + } + return snSigns.at(sn); + } + /** * Sets the admin address. @@ -77,19 +117,40 @@ public void setAdmin(Address _admin) { /** * Retrieves the admin address. * - * @return The admin address. + * @return admin address. */ @External(readonly = true) public Address getAdmin() { return admin.get(); } + /** + * Retrieves the packets dictionary. + * * + * @param nid network id of the source chain + * @return list of mapping from sn to packet data. + */ + protected DictDB getPackets(String nid) { + return packets.at(nid); + } + /** * Checks if the caller of the function is the admin. * * @return true if the caller is the admin, false otherwise */ private void adminOnly() { - Context.require(Context.getCaller().equals(admin.get()), "Unauthorized to call this method"); + Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the leader relayer"); + } + + /** + * Checks if the caller of the function is among the relayers. + * + * @return true if the caller is among the relayers, otherwise false + */ + private void relayersOnly() { + Address caller = Context.getCaller(); + boolean isRelayer = relayers.get(caller); + Context.require(isRelayer, "Unauthorized: caller is not a registered relayer"); } } \ No newline at end of file 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 50dcb21f..cb2784ce 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -1,11 +1,25 @@ package relay.aggregator; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.PrivateKey; +import java.security.Security; +import java.security.AlgorithmParameters; +import java.security.KeyFactory; +import java.security.Signature; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; + import score.Address; import score.UserRevertedException; +import score.DictDB; import com.iconloop.score.test.Account; import com.iconloop.score.test.Score; @@ -14,20 +28,34 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; class RelayAggregatorTest extends TestBase { - protected final ServiceManager sm = getServiceManager(); + private final ServiceManager sm = getServiceManager(); + + private final Account adminAc = sm.createAccount(); - protected final Account adminAc = sm.createAccount(); - protected final Account relayerAcOne = sm.createAccount(); - protected final Account relayerAcTwo = sm.createAccount(); + private final Account relayerAcOne = sm.getAccount(new Address(HexString.toBytes("hxe794bcf6a92efb5e0b58cfc4728236c650d86dce"))); + private final String relayerAcOnePkey = "40b90166d6ace4acbdc59596fd483e487bf58ec4ae5ff31e4f97f039af5b23f7"; + + private final Account relayerAcTwo = sm.getAccount(new Address(HexString.toBytes("hx3445c3d4341a9b1fc2ae6fd578a4453ab0072c07"))); + private final String relayerAcTwoPkey = "0xb533387c502c39eea62f4c2a5be31e388fa87313438236656b4c71be92fed066"; - protected Score aggregator; + private Score aggregator; + private RelayAggregator aggregatorSpy; @BeforeEach void setup() throws Exception { Address[] relayers = new Address[]{relayerAcOne.getAddress(), relayerAcTwo.getAddress()}; aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); + + aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); + aggregator.setInstance(aggregatorSpy); + + Security.addProvider(new BouncyCastleProvider()); } @Test @@ -47,6 +75,106 @@ public void testSetAdmin_unauthorized() { Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress()); UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Unauthorized to call this method", e.getMessage()); + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + } + + @Test + @SuppressWarnings("unchecked") + public void testRegisterPacket() { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; + + DictDB mockDictDB = mock(DictDB.class); + + when(aggregatorSpy.getPackets(nid)).thenReturn(mockDictDB); + + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + + verify(mockDictDB).set(sn, data); + } + + @Test + public void testRegisterPacket_duplicate() { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; + + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + + Executable action = () -> aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Packet already exists", e.getMessage()); + } + + @Test + @SuppressWarnings("unchecked") + public void testSubmitSignature() throws Exception { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; + + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + + DictDB mockSignatures = mock(DictDB.class); + + + when(aggregatorSpy.getSignatures(nid, sn)).thenReturn(mockSignatures); + + byte[] dataHash = hashData(data); + String sign = signData(dataHash,relayerAcOnePkey); + + aggregator.invoke(relayerAcOne, "submitSignature", nid, sn, sign); + + verify(mockSignatures).set(relayerAcOne.getAddress(), sign); + } + + private static byte[] hashData(byte[] data) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(data); + } + + private static String signData(byte[] dataHash, String pKeyStr) throws Exception { + byte[] pKeyBytes = HexString.toBytes(pKeyStr); + + PrivateKey pKey = parsePrivateKey(pKeyBytes); + + Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); + signature.initSign(pKey); + signature.update(dataHash); + + byte[] sign = signature.sign(); + return HexString.fromBytes(sign); + } + + public static PrivateKey parsePrivateKey(byte[] pKeyBytes) throws Exception { + // Ensure the private key is 32 bytes + if (pKeyBytes.length != 32) { + throw new IllegalArgumentException("Invalid private key length: must be 32 bytes."); + } + + // Create a BigInteger from the private key bytes (unsigned) + BigInteger privateKeyInt = new BigInteger(1, pKeyBytes); + + // Create EC PrivateKeySpec using the secp256k1 curve parameters + ECParameterSpec ecSpec = getSecp256k1Curve(); + ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyInt, ecSpec); + + // Create the KeyFactory for generating EC keys + KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC"); + + // Generate the private key from the spec + return keyFactory.generatePrivate(privateKeySpec); + } + + private static ECParameterSpec getSecp256k1Curve() { + try { + AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC", "BC"); + parameters.init(new ECGenParameterSpec("secp256k1")); + return parameters.getParameterSpec(ECParameterSpec.class); + } catch (Exception e) { + throw new RuntimeException("Failed to get secp256k1 curve parameters", e); + } } } diff --git a/contracts/javascore/keystore.json b/contracts/javascore/keystore.json new file mode 100644 index 00000000..4632fa28 --- /dev/null +++ b/contracts/javascore/keystore.json @@ -0,0 +1 @@ +{"address":"hx5f7afdb96154bbe9dbeb2dde3a58afcb1efbfaff","id":"7a8237b8-a61f-4501-9896-b96a39e9c72b","version":3,"coinType":"icx","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c2e5a31a638992a36e3afe96d38ccfda"},"ciphertext":"68d61f1325e1198ca7fd0ee961997e31541ead36ff24b29fc04e6f39cb05eeac","kdf":"scrypt","kdfparams":{"dklen":32,"n":65536,"r":8,"p":1,"salt":"53663f4c4320aff5"},"mac":"388b1466f2a75736fee1f576e2307da2fcaa0cf3d00b968d3c45a98eb9c06f84"}} \ No newline at end of file From 4bfc41ec9bdb92b5570c789acfa4132312c7b276 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Tue, 24 Sep 2024 14:43:32 +0545 Subject: [PATCH 10/75] fix: use icx wallet --- .../relay/aggregator/RelayAggregator.java | 33 ++--- .../relay/aggregator/RelayAggregatorTest.java | 131 +++++++++--------- 2 files changed, 80 insertions(+), 84 deletions(-) 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 a7a5e979..f50b80ce 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -34,7 +34,7 @@ public class RelayAggregator { private final BranchDB> packets = Context.newBranchDB("packets", byte[].class); - private final BranchDB>> signatures = Context.newBranchDB("signatures", DictDB.class); + private final BranchDB>> signatures = Context.newBranchDB("signatures", byte[].class); public RelayAggregator(Address _admin, Address[] _relayers) { if (admin.get() == null) { @@ -70,36 +70,37 @@ public void registerPacket(String nid, BigInteger sn, byte[] data) { * @param signature packet signature */ @External - public void submitSignature(String nid, BigInteger sn, String signature) { + public void submitSignature(String nid, BigInteger sn, byte[] signature) { relayersOnly(); DictDB packetDict = getPackets(nid); byte[] packetData = packetDict.get(sn); - Context.require(packetData != null, "Packet does not exist"); + Context.require(packetData != null, "Packet not registered"); + byte[] dataHash = Context.hash("sha-256", packetData); - DictDB addressSigns = getSignatures(nid, sn); - + byte[] key = Context.recoverKey("ecdsa-secp256k1", dataHash, signature, true); + Address address = Context.getAddressFromKey(key); Address caller = Context.getCaller(); - String sign = addressSigns.get(caller); - Context.require(sign == null, "Signature already exists."); - addressSigns.set(caller, signature); + Context.require(address.equals(caller), "Invalid signature"); + + byte[] sign = signatures.at(nid).at(sn).get(caller); + Context.require(sign == null, "Signature already exists"); + + setSignature(nid, sn, caller, signature); } /** - * Retrieves the signatures dictionary for the packet. + * Sets the signature for that packet at particular nid, sn and address * * @param nid network ID of the source chain * @param sn sequence number of the source chain message - * @return map of relayer addresses to signatures + * @param addr address of signature setter + * @param sign signature of packet */ - protected DictDB getSignatures(String nid, BigInteger sn) { - BranchDB> snSigns = signatures.at(nid); - if (snSigns == null) { - snSigns = Context.newBranchDB(nid+"-signs", DictDB.class); - } - return snSigns.at(sn); + protected void setSignature(String nid, BigInteger sn, Address addr, byte[] sign) { + signatures.at(nid).at(sn).set(addr, sign); } 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 cb2784ce..6d1c8e4c 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -1,23 +1,13 @@ package relay.aggregator; import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.PrivateKey; -import java.security.Security; -import java.security.AlgorithmParameters; -import java.security.KeyFactory; -import java.security.Signature; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; - import score.Address; +import score.Context; import score.UserRevertedException; import score.DictDB; @@ -25,6 +15,7 @@ import com.iconloop.score.test.Score; import com.iconloop.score.test.ServiceManager; import com.iconloop.score.test.TestBase; +import foundation.icon.icx.KeyWallet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -36,26 +27,34 @@ class RelayAggregatorTest extends TestBase { private final ServiceManager sm = getServiceManager(); - private final Account adminAc = sm.createAccount(); - - private final Account relayerAcOne = sm.getAccount(new Address(HexString.toBytes("hxe794bcf6a92efb5e0b58cfc4728236c650d86dce"))); - private final String relayerAcOnePkey = "40b90166d6ace4acbdc59596fd483e487bf58ec4ae5ff31e4f97f039af5b23f7"; + private KeyWallet admin; + private Account adminAc; - private final Account relayerAcTwo = sm.getAccount(new Address(HexString.toBytes("hx3445c3d4341a9b1fc2ae6fd578a4453ab0072c07"))); - private final String relayerAcTwoPkey = "0xb533387c502c39eea62f4c2a5be31e388fa87313438236656b4c71be92fed066"; + private KeyWallet relayerOne; + private Account relayerOneAc; + + private KeyWallet relayerTwo; + private Account relayerTwoAc; private Score aggregator; private RelayAggregator aggregatorSpy; @BeforeEach void setup() throws Exception { - Address[] relayers = new Address[]{relayerAcOne.getAddress(), relayerAcTwo.getAddress()}; + admin = KeyWallet.create(); + adminAc = sm.getAccount(Address.fromString(admin.getAddress().toString())); + + relayerOne = KeyWallet.create(); + relayerOneAc = sm.getAccount(Address.fromString(relayerOne.getAddress().toString())); + + relayerTwo = KeyWallet.create(); + relayerTwoAc = sm.getAccount(Address.fromString(relayerTwo.getAddress().toString())); + + Address[] relayers = new Address[]{relayerOneAc.getAddress(), relayerTwoAc.getAddress()}; aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); aggregator.setInstance(aggregatorSpy); - - Security.addProvider(new BouncyCastleProvider()); } @Test @@ -109,72 +108,68 @@ public void testRegisterPacket_duplicate() { } @Test - @SuppressWarnings("unchecked") public void testSubmitSignature() throws Exception { String nid = "0x2.icon"; BigInteger sn = BigInteger.ONE; byte[] data = new byte[]{0x01, 0x02}; aggregator.invoke(adminAc, "registerPacket", nid, sn, data); - - DictDB mockSignatures = mock(DictDB.class); - - when(aggregatorSpy.getSignatures(nid, sn)).thenReturn(mockSignatures); + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash);; - byte[] dataHash = hashData(data); - String sign = signData(dataHash,relayerAcOnePkey); + aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); - aggregator.invoke(relayerAcOne, "submitSignature", nid, sn, sign); - - verify(mockSignatures).set(relayerAcOne.getAddress(), sign); + verify(aggregatorSpy).setSignature(nid, sn, relayerOneAc.getAddress(), sign); } - private static byte[] hashData(byte[] data) throws Exception { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return digest.digest(data); - } + @Test + public void testSubmitSignature_duplicate() throws Exception { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; - private static String signData(byte[] dataHash, String pKeyStr) throws Exception { - byte[] pKeyBytes = HexString.toBytes(pKeyStr); - - PrivateKey pKey = parsePrivateKey(pKeyBytes); - - Signature signature = Signature.getInstance("SHA256withECDSA", "BC"); - signature.initSign(pKey); - signature.update(dataHash); - - byte[] sign = signature.sign(); - return HexString.fromBytes(sign); - } + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); - public static PrivateKey parsePrivateKey(byte[] pKeyBytes) throws Exception { - // Ensure the private key is 32 bytes - if (pKeyBytes.length != 32) { - throw new IllegalArgumentException("Invalid private key length: must be 32 bytes."); - } + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash);; - // Create a BigInteger from the private key bytes (unsigned) - BigInteger privateKeyInt = new BigInteger(1, pKeyBytes); + aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); - // Create EC PrivateKeySpec using the secp256k1 curve parameters - ECParameterSpec ecSpec = getSecp256k1Curve(); - ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(privateKeyInt, ecSpec); + Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Signature already exists", e.getMessage()); + } - // Create the KeyFactory for generating EC keys - KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", "BC"); + @Test + public void testSubmitSignature_packetUnregistered() throws Exception { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; - // Generate the private key from the spec - return keyFactory.generatePrivate(privateKeySpec); + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash);; + + Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Packet not registered", e.getMessage()); } - private static ECParameterSpec getSecp256k1Curve() { - try { - AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC", "BC"); - parameters.init(new ECGenParameterSpec("secp256k1")); - return parameters.getParameterSpec(ECParameterSpec.class); - } catch (Exception e) { - throw new RuntimeException("Failed to get secp256k1 curve parameters", e); - } + @Test + public void testSubmitSignature_invalidSignatureWithWrongData() throws Exception { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; + + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + + byte[] wrongData = new byte[]{0x01, 0x02, 0x03}; + byte[] dataHash = Context.hash("sha-256", wrongData); + byte[] sign = relayerOne.sign(dataHash);; + + Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Invalid signature", e.getMessage()); } } From d9f7eda452689764b1bbc352058e35fe84ab0e8b Mon Sep 17 00:00:00 2001 From: sherpalden Date: Tue, 24 Sep 2024 22:57:01 +0545 Subject: [PATCH 11/75] fix: emit required events --- .../relay/aggregator/RelayAggregator.java | 134 +++++++++++++----- .../relay/aggregator/RelayAggregatorTest.java | 25 +++- 2 files changed, 124 insertions(+), 35 deletions(-) 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 f50b80ce..576a17eb 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -19,18 +19,21 @@ import java.math.BigInteger; +import s.java.lang.Integer; import score.Context; import score.Address; +import score.ArrayDB; import score.VarDB; import score.DictDB; import score.BranchDB; - +import score.annotation.EventLog; import score.annotation.External; +import scorex.util.ArrayList; public class RelayAggregator { private final VarDB
admin = Context.newVarDB("admin", Address.class); - private final DictDB relayers = Context.newDictDB("relayers", Boolean.class); + private final ArrayDB
relayers = Context.newArrayDB("relayers", Address.class); private final BranchDB> packets = Context.newBranchDB("packets", byte[].class); @@ -40,11 +43,32 @@ public RelayAggregator(Address _admin, Address[] _relayers) { if (admin.get() == null) { admin.set(_admin); for (Address relayer : _relayers) { - relayers.set(relayer, true); + relayers.add(relayer); } } } + /** + * Sets the admin address. + * + * @param _admin the new admin address + */ + @External + public void setAdmin(Address _admin) { + adminOnly(); + admin.set(_admin); + } + + /** + * Retrieves the admin address. + * + * @return admin address. + */ + @External(readonly = true) + public Address getAdmin() { + return admin.get(); + } + /** * Registers a new packet. * @@ -60,6 +84,8 @@ public void registerPacket(String nid, BigInteger sn, byte[] data) { Context.require(pkt == null, "Packet already exists"); packetDict.set(sn, data); + + PacketRegistered(nid, sn, data); } /** @@ -89,43 +115,35 @@ public void submitSignature(String nid, BigInteger sn, byte[] signature) { Context.require(sign == null, "Signature already exists"); setSignature(nid, sn, caller, signature); - } - /** - * Sets the signature for that packet at particular nid, sn and address - * - * @param nid network ID of the source chain - * @param sn sequence number of the source chain message - * @param addr address of signature setter - * @param sign signature of packet - */ - protected void setSignature(String nid, BigInteger sn, Address addr, byte[] sign) { - signatures.at(nid).at(sn).set(addr, sign); - } - - - /** - * Sets the admin address. - * - * @param _admin the new admin address - */ - @External - public void setAdmin(Address _admin) { - adminOnly(); - admin.set(_admin); - } + if (signatureThresholdReached(nid, sn)) { + PacketConfirmed(nid, sn, packetData); + } + } /** - * Retrieves the admin address. - * - * @return admin address. + * Retrieves the list of signatures for the particular packet. + * * + * @param nid network id of the source chain + * @param sn sequence number of packet on source chain + * @return list of signatures */ @External(readonly = true) - public Address getAdmin() { - return admin.get(); + public ArrayList getSignatures(String nid, BigInteger sn) { + DictDB signDict = signatures.at(nid).at(sn); + ArrayList signatureList = new ArrayList(); + + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + byte[] sign = signDict.get(relayer); + if (sign != null) { + signatureList.add(sign); + } + } + return signatureList; } - /** + /** * Retrieves the packets dictionary. * * * @param nid network id of the source chain @@ -135,6 +153,18 @@ protected DictDB getPackets(String nid) { return packets.at(nid); } + /** + * Sets the signature for that packet at particular nid, sn and address + * + * @param nid network ID of the source chain + * @param sn sequence number of the source chain message + * @param addr address of signature setter + * @param sign signature of packet + */ + protected void setSignature(String nid, BigInteger sn, Address addr, byte[] sign) { + signatures.at(nid).at(sn).set(addr, sign); + } + /** * Checks if the caller of the function is the admin. * @@ -151,7 +181,43 @@ private void adminOnly() { */ private void relayersOnly() { Address caller = Context.getCaller(); - boolean isRelayer = relayers.get(caller); + Boolean isRelayer = false; + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + if (relayer.equals(caller)){ + isRelayer = true; + break; + } + } Context.require(isRelayer, "Unauthorized: caller is not a registered relayer"); } + + /** + * Checks if the number of signatures reached the threshold for the packet. + * + * @param nid network ID of the source chain + * @param sn sequence number of the source chain message + * + * @return true if the number of signatures reached the threshold. + */ + private Boolean signatureThresholdReached(String nid, BigInteger sn) { + int noOfSignatures = 0; + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + byte[] relayerSign = signatures.at(nid).at(sn).get(relayer); + if (relayerSign != null) { + noOfSignatures++; + } + } + int threshold = (relayers.size() * 66) / 100; + return noOfSignatures >= threshold; + } + + @EventLog(indexed = 2) + public void PacketRegistered(String srcNetwork, BigInteger srcSn, byte[] data) { + } + + @EventLog(indexed = 1) + public void PacketConfirmed(String srcNetwork, BigInteger srcSn, byte[] data) { + } } \ No newline at end of file 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 6d1c8e4c..9261a3a5 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -36,6 +36,9 @@ class RelayAggregatorTest extends TestBase { private KeyWallet relayerTwo; private Account relayerTwoAc; + private KeyWallet relayerThree; + private Account relayerThreeAc; + private Score aggregator; private RelayAggregator aggregatorSpy; @@ -50,6 +53,9 @@ void setup() throws Exception { relayerTwo = KeyWallet.create(); relayerTwoAc = sm.getAccount(Address.fromString(relayerTwo.getAddress().toString())); + relayerThree = KeyWallet.create(); + relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); + Address[] relayers = new Address[]{relayerOneAc.getAddress(), relayerTwoAc.getAddress()}; aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); @@ -116,13 +122,29 @@ public void testSubmitSignature() throws Exception { aggregator.invoke(adminAc, "registerPacket", nid, sn, data); byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerOne.sign(dataHash);; + byte[] sign = relayerOne.sign(dataHash); aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); verify(aggregatorSpy).setSignature(nid, sn, relayerOneAc.getAddress(), sign); } + @Test + public void testSubmitSignature_unauthorized() throws Exception { + String nid = "0x2.icon"; + BigInteger sn = BigInteger.ONE; + byte[] data = new byte[]{0x01, 0x02}; + + aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerThree.sign(dataHash); + + Executable action = () -> aggregator.invoke(relayerThreeAc, "submitSignature", nid, sn, sign); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", e.getMessage()); + } + @Test public void testSubmitSignature_duplicate() throws Exception { String nid = "0x2.icon"; @@ -172,4 +194,5 @@ public void testSubmitSignature_invalidSignatureWithWrongData() throws Exception assertEquals("Reverted(0): Invalid signature", e.getMessage()); } + } From 7f383dab0f27b0132bc73b1eb8acfa71301f965c Mon Sep 17 00:00:00 2001 From: sherpalden Date: Tue, 24 Sep 2024 22:58:01 +0545 Subject: [PATCH 12/75] fix: remove unused import --- .../src/main/java/relay/aggregator/RelayAggregator.java | 1 - 1 file changed, 1 deletion(-) 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 576a17eb..a3e159ca 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -19,7 +19,6 @@ import java.math.BigInteger; -import s.java.lang.Integer; import score.Context; import score.Address; import score.ArrayDB; From c3bf1f39217cc058c46ad56937f8693d5f289a78 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Wed, 25 Sep 2024 20:24:12 +0545 Subject: [PATCH 13/75] refactor: improve data structures --- .../main/java/relay/aggregator/Packet.java | 135 +++++++++++++++ .../relay/aggregator/RelayAggregator.java | 162 ++++++------------ .../relay/aggregator/RelayAggregatorTest.java | 138 +++++++-------- 3 files changed, 257 insertions(+), 178 deletions(-) create mode 100644 contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java new file mode 100644 index 00000000..64c6b20a --- /dev/null +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java @@ -0,0 +1,135 @@ +package relay.aggregator; + +import java.math.BigInteger; + +import score.ObjectReader; +import score.ObjectWriter; + +public class Packet { + + /** + * The ID of the source network (chain) from where the packet originated. + */ + private final String srcNetwork; + + /** + * The contract address on the source network (chain). + */ + private final String contractAddress; + + /** + * The sequence number of the packet in the source network (chain). + */ + private final BigInteger srcSn; + + /** + * The ID of the destination network (chain) where the packet is being sent. + */ + private final String dstNetwork; + + /** + * The payload data associated with this packet. + */ + private final byte[] data; + + /** + * Constructs a new {@code Packet} object with the specified {@code PacketID}, + * destination network, and data. + * All parameters must be non-null. + * + * @param id the unique identifier for the packet. + * @param dstNetwork the ID of the destination network (chain). + * @param data the payload data for this packet. + * @throws IllegalArgumentException if {@code srcNetwork}, + * {@code contractAddress}, {@code srcSn}, + * {@code dstNetwork}, or {@code data} is + * {@code null}. + */ + public Packet(String srcNetwork, String contractAddress, BigInteger srcSn, String dstNetwork, byte[] data) { + if (srcNetwork == null || contractAddress == null || contractAddress == null || srcSn == null + || dstNetwork == null || data == null) { + throw new IllegalArgumentException( + "srcNetwork, contractAddress, srcSn, dstNetwork, and data cannot be null"); + } + this.srcNetwork = srcNetwork; + this.contractAddress = contractAddress; + this.srcSn = srcSn; + this.dstNetwork = dstNetwork; + this.data = data.clone(); // clone for immutability + } + + public String getId() { + return createId(this.srcNetwork, this.contractAddress, this.srcSn); + } + + public static String createId(String srcNetwork, String contractAddress, BigInteger srcSn) { + return srcNetwork + "/" + contractAddress + "/" + srcSn.toString(); + } + + /** + * Returns the source network (chain) from where the packet originated. + * + * @return the source network ID. + */ + public String getSrcNetwork() { + return srcNetwork; + } + + /** + * Returns the contract address on the source network (chain). + * + * @return the contract address. + */ + public String getContractAddress() { + return contractAddress; + } + + /** + * Returns the sequence number of the packet in the source network (chain). + * + * @return the sequence number. + */ + public BigInteger getSrcSn() { + return srcSn; + } + + /** + * Returns the destination network (chain) where the packet is being sent. + * + * @return the destination network ID. + */ + public String getDstNetwork() { + return dstNetwork; + } + + /** + * Returns a copy of the data associated with this packet. + * + * @return a byte array containing the packet data. + */ + public byte[] getData() { + return data.clone(); // return a clone to preserve immutability + } + + public static void writeObject(ObjectWriter w, Packet p) { + w.beginList(5); + w.write(p.srcNetwork); + w.write(p.contractAddress); + w.write(p.srcSn); + w.write(p.dstNetwork); + w.writeNullable(p.data); + w.end(); + } + + public static Packet readObject(ObjectReader r) { + r.beginList(); + Packet p = new Packet( + r.readString(), + r.readString(), + r.readBigInteger(), + r.readString(), + r.readNullable(byte[].class)); + r.end(); + return p; + } +} 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 a3e159ca..bacf3b0f 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -16,7 +16,6 @@ package relay.aggregator; - import java.math.BigInteger; import score.Context; @@ -34,9 +33,10 @@ public class RelayAggregator { private final ArrayDB
relayers = Context.newArrayDB("relayers", Address.class); - private final BranchDB> packets = Context.newBranchDB("packets", byte[].class); + private final DictDB packets = Context.newDictDB("packets", Packet.class); - private final BranchDB>> signatures = Context.newBranchDB("signatures", byte[].class); + private final BranchDB> signatures = Context.newBranchDB("signatures", + byte[].class); public RelayAggregator(Address _admin, Address[] _relayers) { if (admin.get() == null) { @@ -47,89 +47,69 @@ public RelayAggregator(Address _admin, Address[] _relayers) { } } - /** - * Sets the admin address. - * - * @param _admin the new admin address - */ @External public void setAdmin(Address _admin) { adminOnly(); admin.set(_admin); } - /** - * Retrieves the admin address. - * - * @return admin address. - */ @External(readonly = true) public Address getAdmin() { return admin.get(); } - /** - * Registers a new packet. - * - * @param nid network ID - * @param sn sequence number - * @param data packet data - */ @External - public void registerPacket(String nid, BigInteger sn, byte[] data) { + public void registerPacket( + String srcNetwork, + String contractAddress, + BigInteger srcSn, + String dstNetwork, + byte[] data) { + adminOnly(); - DictDB packetDict = getPackets(nid); - byte[] pkt = packetDict.get(sn); - Context.require(pkt == null, "Packet already exists"); - packetDict.set(sn, data); + Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, dstNetwork, data); + String id = pkt.getId(); - PacketRegistered(nid, sn, data); - } + Context.require(packets.get(id) == null, "Packet already exists"); - /** - * Submits a signature for the registered packet. - * - * @param nid network ID - * @param sn sequence number - * @param signature packet signature - */ - @External - public void submitSignature(String nid, BigInteger sn, byte[] signature) { - relayersOnly(); + packets.set(id, pkt); - DictDB packetDict = getPackets(nid); - byte[] packetData = packetDict.get(sn); - Context.require(packetData != null, "Packet not registered"); + PacketRegistered(pkt.getSrcNetwork(), pkt.getContractAddress(), pkt.getSrcSn()); + } - byte[] dataHash = Context.hash("sha-256", packetData); + @External + public void submitSignature( + String srcNetwork, + String contractAddress, + BigInteger srcSn, + byte[] signature) { - byte[] key = Context.recoverKey("ecdsa-secp256k1", dataHash, signature, true); - Address address = Context.getAddressFromKey(key); - Address caller = Context.getCaller(); + relayersOnly(); - Context.require(address.equals(caller), "Invalid signature"); + String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + Packet pkt = packets.get(pktID); + Context.require(pkt != null, "Packet not registered"); - byte[] sign = signatures.at(nid).at(sn).get(caller); - Context.require(sign == null, "Signature already exists"); + byte[] existingSign = signatures.at(pktID).get(Context.getCaller()); + Context.require(existingSign == null, "Signature already exists"); - setSignature(nid, sn, caller, signature); + setSignature(pktID, Context.getCaller(), signature); - if (signatureThresholdReached(nid, sn)) { - PacketConfirmed(nid, sn, packetData); + if (signatureThresholdReached(pktID)) { + PacketConfirmed( + pkt.getSrcNetwork(), + pkt.getContractAddress(), + pkt.getSrcSn(), + pkt.getDstNetwork(), + pkt.getData()); } - } - - /** - * Retrieves the list of signatures for the particular packet. - * * - * @param nid network id of the source chain - * @param sn sequence number of packet on source chain - * @return list of signatures - */ + } + @External(readonly = true) - public ArrayList getSignatures(String nid, BigInteger sn) { - DictDB signDict = signatures.at(nid).at(sn); + public ArrayList getSignatures(String srcNetwork, String contractAddress, BigInteger srcSn) { + String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + DictDB signDict = signatures.at(pktID); ArrayList signatureList = new ArrayList(); for (int i = 0; i < relayers.size(); i++) { @@ -142,48 +122,20 @@ public ArrayList getSignatures(String nid, BigInteger sn) { return signatureList; } - /** - * Retrieves the packets dictionary. - * * - * @param nid network id of the source chain - * @return list of mapping from sn to packet data. - */ - protected DictDB getPackets(String nid) { - return packets.at(nid); + protected void setSignature(String pktID, Address addr, byte[] sign) { + signatures.at(pktID).set(addr, sign); } - /** - * Sets the signature for that packet at particular nid, sn and address - * - * @param nid network ID of the source chain - * @param sn sequence number of the source chain message - * @param addr address of signature setter - * @param sign signature of packet - */ - protected void setSignature(String nid, BigInteger sn, Address addr, byte[] sign) { - signatures.at(nid).at(sn).set(addr, sign); - } - - /** - * Checks if the caller of the function is the admin. - * - * @return true if the caller is the admin, false otherwise - */ private void adminOnly() { Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the leader relayer"); } - /** - * Checks if the caller of the function is among the relayers. - * - * @return true if the caller is among the relayers, otherwise false - */ private void relayersOnly() { Address caller = Context.getCaller(); Boolean isRelayer = false; for (int i = 0; i < relayers.size(); i++) { Address relayer = relayers.get(i); - if (relayer.equals(caller)){ + if (relayer.equals(caller)) { isRelayer = true; break; } @@ -191,19 +143,11 @@ private void relayersOnly() { Context.require(isRelayer, "Unauthorized: caller is not a registered relayer"); } - /** - * Checks if the number of signatures reached the threshold for the packet. - * - * @param nid network ID of the source chain - * @param sn sequence number of the source chain message - * - * @return true if the number of signatures reached the threshold. - */ - private Boolean signatureThresholdReached(String nid, BigInteger sn) { + private Boolean signatureThresholdReached(String pktID) { int noOfSignatures = 0; for (int i = 0; i < relayers.size(); i++) { Address relayer = relayers.get(i); - byte[] relayerSign = signatures.at(nid).at(sn).get(relayer); + byte[] relayerSign = signatures.at(pktID).get(relayer); if (relayerSign != null) { noOfSignatures++; } @@ -213,10 +157,18 @@ private Boolean signatureThresholdReached(String nid, BigInteger sn) { } @EventLog(indexed = 2) - public void PacketRegistered(String srcNetwork, BigInteger srcSn, byte[] data) { + public void PacketRegistered( + String srcNetwork, + String contractAddress, + BigInteger srcSn) { } - @EventLog(indexed = 1) - public void PacketConfirmed(String srcNetwork, BigInteger srcSn, byte[] data) { + @EventLog(indexed = 2) + public void PacketConfirmed( + String srcNetwork, + String contractAddress, + BigInteger srcSn, + String dstNetwork, + byte[] data) { } } \ No newline at end of file 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 9261a3a5..d3369644 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -9,7 +9,6 @@ import score.Address; import score.Context; import score.UserRevertedException; -import score.DictDB; import com.iconloop.score.test.Account; import com.iconloop.score.test.Score; @@ -19,25 +18,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; class RelayAggregatorTest extends TestBase { private final ServiceManager sm = getServiceManager(); - private KeyWallet admin; - private Account adminAc; - - private KeyWallet relayerOne; - private Account relayerOneAc; + private KeyWallet admin; + private Account adminAc; - private KeyWallet relayerTwo; - private Account relayerTwoAc; + private KeyWallet relayerOne; + private Account relayerOneAc; - private KeyWallet relayerThree; - private Account relayerThreeAc; + private KeyWallet relayerTwo; + private Account relayerTwoAc; + + private KeyWallet relayerThree; + private Account relayerThreeAc; private Score aggregator; private RelayAggregator aggregatorSpy; @@ -56,7 +53,7 @@ void setup() throws Exception { relayerThree = KeyWallet.create(); relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); - Address[] relayers = new Address[]{relayerOneAc.getAddress(), relayerTwoAc.getAddress()}; + Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress() }; aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); @@ -76,123 +73,118 @@ public void testSetAdmin() { public void testSetAdmin_unauthorized() { Account normalAc = sm.createAccount(); Account newAdminAc = sm.createAccount(); - + 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()); } @Test - @SuppressWarnings("unchecked") public void testRegisterPacket() { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; - - DictDB mockDictDB = mock(DictDB.class); - - when(aggregatorSpy.getPackets(nid)).thenReturn(mockDictDB); + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); - verify(mockDictDB).set(sn, data); + verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn); } @Test public void testRegisterPacket_duplicate() { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); - Executable action = () -> aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + Executable action = () -> aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, + dstNetwork, data); UserRevertedException e = assertThrows(UserRevertedException.class, action); - + assertEquals("Reverted(0): Packet already exists", e.getMessage()); } @Test public void testSubmitSignature() throws Exception { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, sign); - verify(aggregatorSpy).setSignature(nid, sn, relayerOneAc.getAddress(), sign); + String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); } @Test public void testSubmitSignature_unauthorized() throws Exception { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerThree.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerThreeAc, "submitSignature", nid, sn, sign); + Executable action = () -> aggregator.invoke(relayerThreeAc, "submitSignature", srcNetwork, contractAddress, + srcSn, + sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", e.getMessage()); + assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", + e.getMessage()); } @Test public void testSubmitSignature_duplicate() throws Exception { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerOne.sign(dataHash);; + byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, sign); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, + sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Signature already exists", e.getMessage()); } @Test public void testSubmitSignature_packetUnregistered() throws Exception { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; + String srcNetwork = "0x2.icon"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerOne.sign(dataHash);; + byte[] sign = relayerOne.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); + Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, + sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Packet not registered", e.getMessage()); } - @Test - public void testSubmitSignature_invalidSignatureWithWrongData() throws Exception { - String nid = "0x2.icon"; - BigInteger sn = BigInteger.ONE; - byte[] data = new byte[]{0x01, 0x02}; - - aggregator.invoke(adminAc, "registerPacket", nid, sn, data); - - byte[] wrongData = new byte[]{0x01, 0x02, 0x03}; - byte[] dataHash = Context.hash("sha-256", wrongData); - byte[] sign = relayerOne.sign(dataHash);; - - Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", nid, sn, sign); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): Invalid signature", e.getMessage()); - } - } From 24813ff09d0dfca2716a29d1b730d158f4867ceb Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 09:06:36 +0545 Subject: [PATCH 14/75] feat: added cluster connection contract for icon --- .../javascore/cluster-connection/build.gradle | 55 +++ .../adapter/cluster/ClusterConnection.java | 312 ++++++++++++++++++ .../cluster/ClusterConnectionTest.java | 297 +++++++++++++++++ contracts/javascore/settings.gradle | 4 +- 4 files changed, 667 insertions(+), 1 deletion(-) create mode 100644 contracts/javascore/cluster-connection/build.gradle create mode 100644 contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java create mode 100644 contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java diff --git a/contracts/javascore/cluster-connection/build.gradle b/contracts/javascore/cluster-connection/build.gradle new file mode 100644 index 00000000..91358eed --- /dev/null +++ b/contracts/javascore/cluster-connection/build.gradle @@ -0,0 +1,55 @@ +version = '0.1.0' + +dependencies { + implementation project(':xcall-lib') + + testImplementation 'org.bouncycastle:bcprov-jdk15on:1.70' + testImplementation 'foundation.icon:javaee-unittest:0.11.1' + testImplementation project(':test-lib') +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport +} + +optimizedJar { + dependsOn(project(':xcall-lib').jar) + mainClassName = 'xcall.adapter.cluster.ClusterConnection' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +deployJar { + endpoints { + berlin { + uri = 'https://berlin.net.solidwallet.io/api/v3' + nid = 0x7 + } + lisbon { + uri = 'https://lisbon.net.solidwallet.io/api/v3' + nid = 0x2 + } + local { + uri = 'http://localhost:9082/api/v3' + nid = 0x3 + } + mainnet { + uri = 'https://ctz.solidwallet.io/api/v3' + nid = 0x1 + } + uat { + uri = project.findProperty('uat.host') as String + nid = property('uat.nid') as Integer + to = "$mockDApp"?:null + } + } + keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : '' + password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' + parameters { + arg('_relayer', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") + arg('_xCall', "$xCall") + } +} \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java new file mode 100644 index 00000000..30affe7c --- /dev/null +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -0,0 +1,312 @@ +/* + * Copyright 2022 ICON Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xcall.adapter.cluster; + +import java.math.BigInteger; +import score.Context; + +import score.Address; +import score.BranchDB; +import score.DictDB; +import score.VarDB; +import score.ArrayDB; +import scorex.util.ArrayList; + +import score.annotation.EventLog; +import score.annotation.External; +import score.annotation.Payable; +import java.util.List; + + + +public class ClusterConnection { + protected final VarDB
xCall = Context.newVarDB("callService", Address.class); + protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); + protected final VarDB reqSignerCnt = Context.newVarDB("reqSignerCnt", BigInteger.class); + private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); + private final ArrayDB
signers = Context.newArrayDB("signers", Address.class); + + protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); + protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); + protected final BranchDB> receipts = Context.newBranchDB("receipts", + Boolean.class); + + public ClusterConnection(Address _relayer, Address _xCall) { + if (xCall.get() == null) { + xCall.set(_xCall); + adminAddress.set(_relayer); + connSn.set(BigInteger.ZERO); + signers.add(_relayer); + SignerAdded(_relayer); + } + } + + /** + * Retrieves the signers. + * + * @return The signers . + */ + @External(readonly = true) + public Address[] listSigners() { + Address[] sgs = new Address[signers.size()]; + for(int i=0; i < signers.size(); i++) { + sgs[i] = signers.get(i); + } + return sgs; + } + + @External + public void addSigner(Address signer) { + OnlyAdmin(); + if (!signerExists(signer)){ + signers.add(signer); + SignerAdded(signer); + } + } + + @External + public void removeSigner(Address _signer) { + OnlyAdmin(); + Context.require(_signer != adminAddress.get(),"cannot remove admin"); + if (signerExists(_signer)){ + Address top = this.signers.pop(); + if (!top.equals(_signer)) { + for (int i = 0; i < this.signers.size(); i++) { + if (_signer.equals(this.signers.get(i))) { + this.signers.set(i, top); + break; + } + } + } + SignerRemoved(_signer); + } + } + + @EventLog(indexed = 2) + public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { + } + + @EventLog(indexed = 1) + public void SignerAdded(Address _signer) { + } + + @EventLog(indexed = 1) + public void SignerRemoved(Address _signer) { + } + + /** + * Sets the admin address. + * + * @param _relayer the new admin address + */ + @External + public void setAdmin(Address _relayer) { + OnlyAdmin(); + adminAddress.set(_relayer); + } + + /** + * Retrieves the admin address. + * + * @return The admin address. + */ + @External(readonly = true) + public Address admin() { + return adminAddress.get(); + } + + /** + * Sets the required signer count + * + * @param _signerCnt the new required signer count + */ + @External + public void setRequiredSignerCount(BigInteger _signerCnt) { + OnlyAdmin(); + reqSignerCnt.set(_signerCnt); + } + + /** + * Retrieves the required signer count. + * + * @return The required signer count. + */ + @External(readonly = true) + public BigInteger requiredSignerCount() { + return reqSignerCnt.get(); + } + + /** + * Sets the fee to the target network + * + * @param networkId String Network Id of target chain + * @param messageFee The fee needed to send a Message + * @param responseFee The fee of the response + */ + @External + public void setFee(String networkId, BigInteger messageFee, BigInteger responseFee) { + OnlyAdmin(); + messageFees.set(networkId, messageFee); + responseFees.set(networkId, responseFee); + } + + /** + * Returns the fee associated with the given destination address. + * + * @param to String Network Id of target chain + * @param response whether the responding fee is included + * @return The fee of sending a message to a given destination network + */ + @External(readonly = true) + public BigInteger getFee(String to, boolean response) { + BigInteger messageFee = messageFees.getOrDefault(to, BigInteger.ZERO); + if (response) { + BigInteger responseFee = responseFees.getOrDefault(to, BigInteger.ZERO); + return messageFee.add(responseFee); + } + return messageFee; + } + + /** + * Sends a message to the specified network. + * + * @param to Network Id of destination network + * @param svc name of the service + * @param sn positive for two-way message, zero for one-way message, negative + * for response(for xcall message) + * @param msg serialized bytes of Service Message + */ + @Payable + @External + public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { + Context.require(Context.getCaller().equals(xCall.get()), "Only xCall can send messages"); + BigInteger fee = BigInteger.ZERO; + if (sn.compareTo(BigInteger.ZERO) > 0) { + fee = getFee(to, true); + } else if (sn.equals(BigInteger.ZERO)) { + fee = getFee(to, false); + } + + BigInteger nextConnSn = connSn.get().add(BigInteger.ONE); + connSn.set(nextConnSn); + + Context.require(Context.getValue().compareTo(fee) >= 0, "Insufficient balance"); + Message(to, nextConnSn, msg); + } + + /** + * Receives a message from a source network. + * + * @param srcNetwork the source network id from which the message is received + * @param _connSn the serial number of the connection message + * @param msg serialized bytes of Service Message + */ + @External + public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, + byte[][] signatures) { + OnlyAdmin(); + List
uniqueSigners = new ArrayList<>(); + for (byte[] signature : signatures) { + Address signer = getSigner(msg, signature); + Context.require(signerExists(signer), "Invalid signer"); + if (!uniqueSigners.contains(signer)){ + uniqueSigners.add(signer); + } + } + if (uniqueSigners.size() >= reqSignerCnt.get().intValue()){ + recvMessage(srcNetwork, _connSn, msg); + } + } + + private boolean signerExists(Address signer) { + for (int i = 0; i < signers.size(); i++) { + if (signers.get(i).equals(signer)) { + return true; + } + } + return false; + } + + /** + * Receives a message from a source network. + * + * @param srcNetwork the source network id from which the message is received + * @param _connSn the serial number of the connection message + * @param msg serialized bytes of Service Message + */ + @External + public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { + OnlyAdmin(); + Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); + receipts.at(srcNetwork).set(_connSn, true); + Context.call(xCall.get(), "handleMessage", srcNetwork, msg); + } + + private Address getSigner(byte[] msg,byte[] sig){ + byte[] hashMessage = getHash(msg); + byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); + return Context.getAddressFromKey(key); + } + + private byte[] getHash(byte[] msg){ + return Context.hash("keccak-256", msg); + } + + /** + * Reverts a message. + * + * @param sn the serial number of xcall message representing the message to + * revert + */ + @External + public void revertMessage(BigInteger sn) { + OnlyAdmin(); + Context.call(xCall.get(), "handleError", sn); + } + + /** + * Claim the fees. + * + */ + @External + public void claimFees() { + OnlyAdmin(); + Context.transfer(admin(), Context.getBalance(Context.getAddress())); + } + + /** + * Get the receipts for a given source network and serial number. + * + * @param srcNetwork the source network id + * @param _connSn the serial number of connection message + * @return the receipt if is has been recived or not + */ + @External(readonly = true) + public boolean getReceipts(String srcNetwork, BigInteger _connSn) { + return receipts.at(srcNetwork).getOrDefault(_connSn, false); + } + + /** + * Checks if the caller of the function is the admin. + * + * @return true if the caller is the admin, false otherwise + */ + private void OnlyAdmin() { + Context.require(Context.getCaller().equals(adminAddress.get()), "Only admin can call this function"); + } + +} \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java new file mode 100644 index 00000000..77cd6396 --- /dev/null +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -0,0 +1,297 @@ +package xcall.adapter.cluster; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.security.*; + +import java.beans.Transient; +import java.math.BigInteger; +import score.Context; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jcajce.provider.digest.Keccak; + +import foundation.icon.icx.KeyWallet; + + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import com.iconloop.score.test.Account; +import com.iconloop.score.test.Score; +import com.iconloop.score.test.ServiceManager; +import com.iconloop.score.test.TestBase; + +import xcall.adapter.cluster.ClusterConnection; +import score.UserRevertedException; +import score.Address; +import foundation.icon.ee.types.Bytes; +import foundation.icon.icx.Call; +import foundation.icon.score.client.RevertedException; +import foundation.icon.xcall.CSMessage; +import foundation.icon.xcall.CSMessageRequest; +import foundation.icon.xcall.CallService; +import foundation.icon.xcall.CallServiceReceiver; +import foundation.icon.xcall.CallServiceScoreInterface; +import foundation.icon.xcall.ConnectionScoreInterface; +import foundation.icon.xcall.Connection; +import foundation.icon.xcall.NetworkAddress; +import s.java.math.BigDecimal; + +import xcall.icon.test.MockContract; + +public class ClusterConnectionTest extends TestBase { + protected final ServiceManager sm = getServiceManager(); + + protected final Account owner = sm.createAccount(); + protected final Account user = sm.createAccount(); + protected final Account admin = sm.createAccount(); + protected final Account xcallMock = sm.createAccount(); + + protected final Account source_relayer = sm.createAccount(); + protected final Account destination_relayer = sm.createAccount(); + + protected Score xcall, connection; + protected CallService xcallSpy; + protected ClusterConnection connectionSpy; + + protected static String nidSource = "nid.source"; + protected static String nidTarget = "nid.target"; + + // static MockedStatic contextMock; + + protected MockContract callservice; + + // @BeforeAll + // protected static void init() { + // contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS); + // } + + @BeforeEach + public void setup() throws Exception { + Security.addProvider(new BouncyCastleProvider()); + callservice = new MockContract<>(CallServiceScoreInterface.class, CallService.class, sm, owner); + + // xcall = sm.deploy(owner, CallService.class, nidSource); + // xcallSpy = (CallService) spy(xcall.getInstance()); + // xcall.setInstance(xcallSpy); + // contextMock.reset(); + + connection = sm.deploy(owner, ClusterConnection.class, source_relayer.getAddress(), + callservice.getAddress()); + connectionSpy = (ClusterConnection) spy(connection.getInstance()); + connection.setInstance(connectionSpy); + } + + @Test + public void testSetAdmin() { + // connection.invoke(source_relayer, "setFee", "0xevm", BigInteger.TEN, + // BigInteger.TEN); + + connection.invoke(source_relayer, "setAdmin", admin.getAddress()); + assertEquals(connection.call("admin"), admin.getAddress()); + } + + @Test + public void testSetAdmin_unauthorized() { + UserRevertedException e = assertThrows(UserRevertedException.class, + () -> connection.invoke(user, "setAdmin", admin.getAddress())); + assertEquals("Reverted(0): " + "Only admin can call this function", e.getMessage()); + } + + @Test + public void setFee() { + connection.invoke(source_relayer, "setFee", nidTarget, BigInteger.TEN, BigInteger.TEN); + assertEquals(connection.call("getFee", nidTarget, true), BigInteger.TEN.add(BigInteger.TEN)); + } + + @Test + public void sendMessage() { + connection.invoke(callservice.account, "sendMessage", nidTarget, "xcall", BigInteger.ONE, "test".getBytes()); + verify(connectionSpy).Message(nidTarget, BigInteger.ONE, "test".getBytes()); + } + + @Test + public void testRecvMessage() { + connection.invoke(source_relayer, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes()); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessage_unauthorized(){ + + UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + } + + @Test + public void testSendMessage_unauthorized() { + UserRevertedException e = assertThrows(UserRevertedException.class, + () -> connection.invoke(user, "sendMessage", nidTarget, "xcall", BigInteger.ONE, "test".getBytes())); + assertEquals("Reverted(0): " + "Only xCall can send messages", e.getMessage()); + } + + @Test + public void testRecvMessage_duplicateMsg(){ + connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); + + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(source_relayer, "recvMessage", + nidSource, BigInteger.ONE, "test".getBytes())); + assertEquals(e.getMessage(), "Reverted(0): "+"Duplicate Message"); + } + + @Test + public void testRevertMessage() { + + connection.invoke(source_relayer, "revertMessage", BigInteger.ONE); + } + + @Test + public void testRevertMessage_unauthorized(){ + UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(user, "revertMessage", BigInteger.ONE)); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + + } + + @Test + public void testSetFeesUnauthorized(){ + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "setFee", "0xevm", + BigInteger.TEN, BigInteger.TEN)); + assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + } + + @Test + public void testClaimFees(){ + setFee(); + connection.invoke(source_relayer, "claimFees"); + assertEquals(source_relayer.getBalance(), BigInteger.ZERO); + + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(callservice.account, "sendMessage", nidTarget, + "xcall", BigInteger.ONE, "null".getBytes())); + assertEquals(e.getMessage(), "Reverted(0): Insufficient balance"); + + try (MockedStatic contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS)) { + contextMock.when(() -> Context.getValue()).thenReturn(BigInteger.valueOf(20)); + connection.invoke(callservice.account, "sendMessage", nidTarget,"xcall", BigInteger.ONE, "null".getBytes()); + } + + + try (MockedStatic contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS)) { + contextMock.when(() -> Context.getBalance(connection.getAddress())).thenReturn(BigInteger.valueOf(20)); + contextMock.when(() -> Context.transfer(source_relayer.getAddress(),BigInteger.valueOf(20))).then(invocationOnMock -> null); + connection.invoke(source_relayer, "claimFees"); + } + } + + @Test + public void testClaimFees_unauthorized(){ + setFee(); + UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "claimFees")); + assertEquals(e.getMessage(), "Reverted(0): "+"Only admin can call this function"); + } + + public MockedStatic.Verification value() { + return () -> Context.getValue(); + } + + @Test + public void testGetReceipt(){ + assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), + false); + + connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); + + assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), + true); + } + + @Test + public void testRecvMessageWithSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + byte[][] byteArray = new byte[1][]; + KeyWallet wallet = KeyWallet.create(); + byteArray[0] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.ONE); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessageWithMultiSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + byte[][] byteArray = new byte[2][]; + KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); + byteArray[0] = wallet.sign(messageHash); + byteArray[1] = wallet2.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); + } + + @Test + public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + KeyWallet wallet = KeyWallet.create(); + byte[][] byteArray = new byte[1][]; + byteArray[0] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + verifyNoInteractions(callservice.mock); + } + + + // Hash the message with Keccak-256 + public static byte[] keccak256(byte[] input) { + Keccak.Digest256 keccak256 = new Keccak.Digest256(); + return keccak256.digest(input); + } + + @Test + public void testAddSigners() throws Exception{ + KeyWallet wallet = KeyWallet.create(); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 2); + } + + @Test + public void testAddNRemoveSigners() throws Exception{ + KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); + KeyWallet wallet3 = KeyWallet.create(); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 3); + + connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet3.getAddress().toString())); + signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 3); + + connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet2.getAddress().toString())); + signers = connection.call(Address[].class,"listSigners"); + assertEquals(signers.length, 2); + } + +} \ No newline at end of file diff --git a/contracts/javascore/settings.gradle b/contracts/javascore/settings.gradle index 6e6fdcdf..e654c0c0 100644 --- a/contracts/javascore/settings.gradle +++ b/contracts/javascore/settings.gradle @@ -3,7 +3,9 @@ include( 'test-lib', 'xcall', 'xcall-lib', - 'centralized-connection' + 'centralized-connection', + 'cluster-connection' + ) include(':dapp-simple') From 6937c2c8991c9449df2984318f04da3322de5958 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 09:25:47 +0545 Subject: [PATCH 15/75] feat: added cluster connection contract for evm --- .../contracts/adapters/ClusterConnection.sol | 256 +++++++++++ contracts/evm/forge_build.sh | 1 + .../evm/test/adapters/ClusterConnection.t.sol | 418 ++++++++++++++++++ scripts/optimize-solidity.sh | 2 +- 4 files changed, 676 insertions(+), 1 deletion(-) create mode 100644 contracts/evm/contracts/adapters/ClusterConnection.sol create mode 100644 contracts/evm/test/adapters/ClusterConnection.t.sol diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol new file mode 100644 index 00000000..22c0f290 --- /dev/null +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0; +pragma abicoder v2; + +import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; +import "@xcall/utils/Types.sol"; +import "@xcall/contracts/xcall/interfaces/IConnection.sol"; +import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; + +contract ClusterConnection is Initializable, IConnection { + mapping(string => uint256) private messageFees; + mapping(string => uint256) private responseFees; + mapping(string => mapping(uint256 => bool)) receipts; + mapping(address => bool) public isSigner; + + address private xCall; + address private adminAddress; + uint256 public connSn; + address[] private signers; + uint8 private reqSigCount; + + event Message(string targetNetwork, uint256 sn, bytes _msg); + event SignerAdded(address _signer); + event SignerRemoved(address _signer); + + modifier onlyAdmin() { + require(msg.sender == this.admin(), "OnlyRelayer"); + _; + } + + function initialize(address _relayer, address _xCall) public initializer { + xCall = _xCall; + signers.push(_relayer); + adminAddress = _relayer; + emit SignerAdded(_relayer); + } + + function listSigners() external view returns (address[] memory) { + return signers; + } + + function addSigner(address _newSigner) external onlyAdmin { + require(!isSigner[_newSigner], "Address is already an signer"); + signers.push(_newSigner); + isSigner[_newSigner] = true; + emit SignerAdded(_newSigner); + } + + // Function to remove an existing signer + function removeSigner(address _signer) external onlyAdmin { + require(_signer!=this.admin(), "cannot remove admin"); + for (uint i = 0; i < signers.length; i++) { + if (signers[i] == _signer) { + signers[i] = signers[signers.length - 1]; + signers.pop(); + isSigner[_signer] = false; + break; + } + emit SignerRemoved(_signer); + } + } + + /** + @notice Sets the fee to the target network + @param networkId String Network Id of target chain + @param messageFee Integer ( The fee needed to send a Message ) + @param responseFee Integer (The fee of the response ) + */ + function setFee( + string calldata networkId, + uint256 messageFee, + uint256 responseFee + ) external onlyAdmin { + messageFees[networkId] = messageFee; + responseFees[networkId] = responseFee; + } + + /** + @notice Gets the fee to the target network + @param to String Network Id of target chain + @param response Boolean ( Whether the responding fee is included ) + @return fee Integer (The fee of sending a message to a given destination network ) + */ + function getFee( + string memory to, + bool response + ) external view returns (uint256 fee) { + uint256 messageFee = messageFees[to]; + if (response == true) { + uint256 responseFee = responseFees[to]; + return messageFee + responseFee; + } + return messageFee; + } + + /** + @notice Sends the message to a specific network. + @param sn : positive for two-way message, zero for one-way message, negative for response + @param to String ( Network Id of destination network ) + @param svc String ( name of the service ) + @param sn Integer ( serial number of the xcall message ) + @param _msg Bytes ( serialized bytes of Service Message ) + */ + function sendMessage( + string calldata to, + string calldata svc, + int256 sn, + bytes calldata _msg + ) external payable override { + require(msg.sender == xCall, "Only Xcall can call sendMessage"); + uint256 fee; + if (sn > 0) { + fee = this.getFee(to, true); + } else if (sn == 0) { + fee = this.getFee(to, false); + } + require(msg.value >= fee, "Fee is not Sufficient"); + connSn++; + emit Message(to, connSn, _msg); + } + + /** + @notice Sends the message to a xCall. + @param srcNetwork String ( Network Id ) + @param _connSn Integer ( connection message sn ) + @param _msg Bytes ( serialized bytes of Service Message ) + */ + function recvMessageWithSignatures( + string memory srcNetwork, + uint256 _connSn, + bytes calldata _msg, + bytes[] calldata _signedMessages + ) public onlyAdmin { + require(_signedMessages.length > 0, "No signatures provided"); + require(_signedMessages.length >= reqSigCount, "Not enough signatures passed"); + bytes32 messageHash = keccak256(_msg); + uint signerCount = 0; + address[] memory collectedSigners = new address[](_signedMessages.length); + for (uint i = 0; i < _signedMessages.length; i++) { + address signer = recoverSigner(messageHash, _signedMessages[i]); + require(signer != address(0), "Invalid signature"); + if (!isSignerProcessed(collectedSigners, signer)){ + collectedSigners[signerCount] = signer; + signerCount++; + } + } + + if (signerCount >= reqSigCount) { + recvMessage(srcNetwork,_connSn,_msg); + } + } + + function isSignerProcessed(address[] memory processedSigners, address signer) public pure returns (bool) { + for (uint i = 0; i < processedSigners.length; i++) { + if (processedSigners[i] == signer) { + return true; + } + } + return false; + } + + function recoverSigner(bytes32 messageHash, bytes memory signature) public pure returns (address) { + require(signature.length == 65, "Invalid signature length"); + bytes32 r; + bytes32 s; + uint8 v; + assembly { + r := mload(add(signature, 32)) + s := mload(add(signature, 64)) + v := byte(0, mload(add(signature, 96))) + } + if (v < 27) { + v += 27; + } + require(v == 27 || v == 28, "Invalid signature 'v' value"); + return ecrecover(toEthSignedMessageHash(messageHash), v, r, s); + } + + function toEthSignedMessageHash(bytes32 _messageHash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); + } + + + /** + @notice Sends the message to a xCall. + @param srcNetwork String ( Network Id ) + @param _connSn Integer ( connection message sn ) + @param _msg Bytes ( serialized bytes of Service Message ) + */ + function recvMessage( + string memory srcNetwork, + uint256 _connSn, + bytes calldata _msg + ) public onlyAdmin { + require(!receipts[srcNetwork][_connSn], "Duplicate Message"); + receipts[srcNetwork][_connSn] = true; + ICallService(xCall).handleMessage(srcNetwork, _msg); + } + + /** + @notice Sends the balance of the contract to the owner(relayer) + + */ + function claimFees() public onlyAdmin { + payable(adminAddress).transfer(address(this).balance); + } + + /** + @notice Revert a messages, used in special cases where message can't just be dropped + @param sn Integer ( serial number of the xcall message ) + */ + function revertMessage(uint256 sn) public onlyAdmin { + ICallService(xCall).handleError(sn); + } + + /** + @notice Gets a message receipt + @param srcNetwork String ( Network Id ) + @param _connSn Integer ( connection message sn ) + @return boolean if is has been recived or not + */ + function getReceipt( + string memory srcNetwork, + uint256 _connSn + ) public view returns (bool) { + return receipts[srcNetwork][_connSn]; + } + + /** + @notice Set the address of the admin. + @param _address The address of the admin. + */ + function setAdmin(address _address) external onlyAdmin { + adminAddress = _address; + } + + /** + @notice Gets the address of admin + @return (Address) the address of admin + */ + function admin() external view returns (address) { + return adminAddress; + } + + /** + @notice Set the required signature count for verification. + @param _count The desired count. + */ + function setRequiredCount(uint8 _count) external onlyAdmin { + reqSigCount = _count; + } + + function getRequiredCount() external view returns (uint8) { + return reqSigCount; + } +} diff --git a/contracts/evm/forge_build.sh b/contracts/evm/forge_build.sh index 76d931ce..c4bbd8cf 100755 --- a/contracts/evm/forge_build.sh +++ b/contracts/evm/forge_build.sh @@ -5,5 +5,6 @@ forge build mkdir artifacts cat out/CallService.sol/CallService.json | jq '{"abi": .abi, "bytecode": .bytecode.object}' > artifacts/xcall_abi_bytecode.json cat out/CentralizedConnection.sol/CentralizedConnection.json | jq '{"abi": .abi, "bytecode": .bytecode.object}' > artifacts/centralized_connection_abi_byte_code.json +cat out/ClusterConnection.sol/ClusterConnection.json | jq '{"abi": .abi, "bytecode": .bytecode.object}' > artifacts/cluster_connection_abi_byte_code.json cat out/LayerZeroAdapter.sol/LayerZeroAdapter.json | jq '{"abi": .abi, "bytecode": .bytecode.object}' > artifacts/layer_zero_adapter_abi_bytecode.json cat out/WormholeAdapter.sol/WormholeAdapter.json | jq '{"abi": .abi, "bytecode": .bytecode.object}' > artifacts/wormhole_adapter_abi.json diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol new file mode 100644 index 00000000..0251ef4a --- /dev/null +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console2 } from "forge-std/Test.sol"; +import {LZEndpointMock} from "@lz-contracts/mocks/LZEndpointMock.sol"; +import "@xcall/contracts/adapters/ClusterConnection.sol"; +import "@xcall/contracts/xcall/CallService.sol"; +import "@xcall/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol"; +import "@xcall/utils/Types.sol"; + +contract ClusterConnectionTest is Test { + using RLPEncodeStruct for Types.CSMessage; + using RLPEncodeStruct for Types.CSMessageRequestV2; + + event CallExecuted(uint256 indexed _reqId, int _code, string _msg); + + event RollbackExecuted(uint256 indexed _sn); + + event Message(string targetNetwork, int256 sn, bytes msg); + + event ResponseOnHold(uint256 indexed _sn); + + MultiProtocolSampleDapp dappSource; + MultiProtocolSampleDapp dappTarget; + + CallService xCallSource; + CallService xCallTarget; + + ClusterConnection adapterSource; + ClusterConnection adapterTarget; + + address public sourceRelayer; + address public destinationRelayer; + + string public nidSource = "icon.local"; + string public nidTarget = "evm.local"; + + address public owner = address(uint160(uint256(keccak256("owner")))); + address public admin = address(uint160(uint256(keccak256("admin")))); + address public user = address(uint160(uint256(keccak256("user")))); + + event CallMessage( + string indexed _from, + string indexed _to, + uint256 indexed _sn, + uint256 _reqId, + bytes _data + ); + + address public source_relayer = + address(uint160(uint256(keccak256("source_relayer")))); + address public destination_relayer = + address(uint160(uint256(keccak256("destination_relayer")))); + + function _setupSource() internal { + console2.log("------>setting up source<-------"); + xCallSource = new CallService(); + xCallSource.initialize(nidSource); + + dappSource = new MultiProtocolSampleDapp(); + dappSource.initialize(address(xCallSource)); + + adapterSource = new ClusterConnection(); + adapterSource.initialize(source_relayer, address(xCallSource)); + + xCallSource.setDefaultConnection(nidTarget, address(adapterSource)); + + console2.log(ParseAddress.toString(address(xCallSource))); + console2.log(ParseAddress.toString(address(user))); + } + + function _setupTarget() internal { + console2.log("------>setting up target<-------"); + + xCallTarget = new CallService(); + xCallTarget.initialize(nidTarget); + + dappTarget = new MultiProtocolSampleDapp(); + dappTarget.initialize(address(xCallTarget)); + + adapterTarget = new ClusterConnection(); + adapterTarget.initialize(destination_relayer, address(xCallTarget)); + + xCallTarget.setDefaultConnection(nidSource, address(adapterTarget)); + } + + /** + * @dev Sets up the initial state for the test. + */ + function setUp() public { + vm.startPrank(owner); + + _setupSource(); + _setupTarget(); + + vm.stopPrank(); + + // deal some gas + vm.deal(admin, 10 ether); + vm.deal(user, 10 ether); + } + + function testSetAdmin() public { + vm.prank(source_relayer); + adapterSource.setAdmin(user); + assertEq(adapterSource.admin(), user); + } + + function testSetAdminUnauthorized() public { + vm.prank(user); + vm.expectRevert("OnlyRelayer"); + adapterSource.setAdmin(user); + } + + function testSendMessage() public { + vm.startPrank(user); + string memory to = NetworkAddress.networkAddress( + nidTarget, + ParseAddress.toString(address(dappTarget)) + ); + + uint256 cost = adapterSource.getFee(nidTarget, false); + + bytes memory data = bytes("test"); + bytes memory rollback = bytes(""); + + dappSource.sendMessage{value: cost}(to, data, rollback); + vm.stopPrank(); + } + + function testRecvMessage() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + + vm.startPrank(destination_relayer); + adapterTarget.recvMessage( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message) + ); + vm.stopPrank(); + } + + function testRecvMessageUnAuthorized() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + + vm.startPrank(user); + vm.expectRevert("OnlyRelayer"); + adapterTarget.recvMessage( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message) + ); + vm.stopPrank(); + } + + function testRecvMessageDuplicateMsg() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + + vm.startPrank(destination_relayer); + adapterTarget.recvMessage( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message) + ); + + vm.expectRevert("Duplicate Message"); + adapterTarget.recvMessage( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message) + ); + vm.stopPrank(); + } + + function testRevertMessage() public { + vm.startPrank(destination_relayer); + vm.expectRevert("CallRequestNotFound"); + adapterTarget.revertMessage(1); + vm.stopPrank(); + } + + function testRevertMessageUnauthorized() public { + vm.startPrank(user); + vm.expectRevert("OnlyRelayer"); + adapterTarget.revertMessage(1); + vm.stopPrank(); + } + + function testSetFees() public { + vm.prank(source_relayer); + adapterSource.setFee(nidTarget, 5 ether, 5 ether); + + assertEq(adapterSource.getFee(nidTarget, true), 10 ether); + assertEq(adapterSource.getFee(nidTarget, false), 5 ether); + } + + function testSetFeesUnauthorized() public { + vm.prank(user); + + vm.expectRevert("OnlyRelayer"); + adapterSource.setFee(nidTarget, 5 ether, 5 ether); + } + + function testClaimFeesUnauthorized() public { + vm.prank(user); + + vm.expectRevert("OnlyRelayer"); + adapterSource.claimFees(); + } + + function testClaimFees() public { + testSetFees(); + vm.startPrank(user); + string memory to = NetworkAddress.networkAddress( + nidTarget, + ParseAddress.toString(address(dappTarget)) + ); + + uint256 cost = adapterSource.getFee(nidTarget, true); + + bytes memory data = bytes("test"); + bytes memory rollback = bytes("rollback"); + + dappSource.sendMessage{value: cost}(to, data, rollback); + vm.stopPrank(); + + assert(address(adapterSource).balance == 10 ether); + + vm.startPrank(source_relayer); + adapterSource.claimFees(); + vm.stopPrank(); + + assert(source_relayer.balance == 10 ether); + } + + function testGetReceipt() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + + assert(adapterTarget.getReceipt(nidSource, 1) == false); + + vm.startPrank(destination_relayer); + adapterTarget.recvMessage( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message) + ); + vm.stopPrank(); + + assert(adapterTarget.getReceipt(nidSource, 1) == true); + } + + function testRecvMessageWithMultiSignature() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + uint256 pk2 = hexStringToUint256("47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"); + uint256 pk3 = hexStringToUint256("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); + uint256 pk4 = hexStringToUint256("2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"); + bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); + vm.startPrank(destination_relayer); + adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addSigner(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); + adapterTarget.addSigner(address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8)); + adapterTarget.addSigner(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); + adapterTarget.setRequiredCount(4); + vm.expectEmit(); + emit CallMessage(iconDapp, ParseAddress.toString(address(dappSource)), 1, 1, data); + vm.expectCall(address(xCallTarget), abi.encodeCall(xCallTarget.handleMessage, (nidSource,RLPEncodeStruct.encodeCSMessage(message)))); + bytes[] memory signatures = new bytes[](4) ; + signatures[0] = signMessage(pk,hash); + signatures[1] = signMessage(pk2,hash); + signatures[2] = signMessage(pk3,hash); + signatures[3] = signMessage(pk4,hash); + adapterTarget.recvMessageWithSignatures( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message), + signatures + ); + vm.stopPrank(); + } + + function signMessage(uint256 pk,bytes32 hash) private pure returns (bytes memory){ + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, hash); + bytes memory signature = combineSignature(r,s,v); + return signature; + } + + function combineSignature(bytes32 r, bytes32 s, uint8 v) private pure returns (bytes memory) { + return abi.encodePacked(r, s, v); + } + + function hexStringToUint256(string memory hexString) public pure returns (uint256) { + bytes memory hexBytes = bytes(hexString); + uint256 number = 0; + + for (uint i = 0; i < hexBytes.length; i++) { + uint8 hexDigit = uint8(hexBytes[i]); + + // Convert ASCII characters 0-9 and A-F or a-f to their numeric values + if (hexDigit >= 48 && hexDigit <= 57) { + number = number * 16 + (hexDigit - 48); // 0-9 + } else if (hexDigit >= 65 && hexDigit <= 70) { + number = number * 16 + (hexDigit - 55); // A-F + } else if (hexDigit >= 97 && hexDigit <= 102) { + number = number * 16 + (hexDigit - 87); // a-f + } else { + revert("Invalid hex character"); + } + } + return number; + } + + function testAddSigner() public { + vm.startPrank(destination_relayer); + adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + assertEq(2, adapterTarget.listSigners().length); + vm.stopPrank(); + } + + function testRemoveSigner() public { + vm.startPrank(destination_relayer); + adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addSigner(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(3, adapterTarget.listSigners().length); + adapterTarget.removeSigner(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); + assertEq(3, adapterTarget.listSigners().length); + adapterTarget.removeSigner(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(2, adapterTarget.listSigners().length); + vm.stopPrank(); + } + + function testRequiredCount() public { + vm.startPrank(destination_relayer); + adapterTarget.setRequiredCount(3); + assertEq(3, adapterTarget.getRequiredCount()); + vm.stopPrank(); + } +} diff --git a/scripts/optimize-solidity.sh b/scripts/optimize-solidity.sh index 3c344e90..c7a836cc 100755 --- a/scripts/optimize-solidity.sh +++ b/scripts/optimize-solidity.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e # contracts -CONTRACTS=("CallService" "DAppProxySample" "MultiProtocolSampleDapp" "LayerZeroAdapter" "WormholeAdapter" "CentralizedConnection") +CONTRACTS=("CallService" "DAppProxySample" "MultiProtocolSampleDapp" "LayerZeroAdapter" "WormholeAdapter" "CentralizedConnection" "ClusterConnection") # Directory paths build_directory="build" From 370edb798c1165d4209be7f55d7326b9f056ba5d Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 09:34:26 +0545 Subject: [PATCH 16/75] chore: added constraints --- .../adapter/cluster/ClusterConnection.java | 36 ++++++++++--------- .../cluster/ClusterConnectionTest.java | 16 ++------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 30affe7c..51d1ee4f 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -208,29 +208,31 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { Message(to, nextConnSn, msg); } - /** + /** * Receives a message from a source network. * * @param srcNetwork the source network id from which the message is received * @param _connSn the serial number of the connection message * @param msg serialized bytes of Service Message + * @param signatures array of signatures */ - @External - public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, - byte[][] signatures) { - OnlyAdmin(); - List
uniqueSigners = new ArrayList<>(); - for (byte[] signature : signatures) { - Address signer = getSigner(msg, signature); - Context.require(signerExists(signer), "Invalid signer"); - if (!uniqueSigners.contains(signer)){ - uniqueSigners.add(signer); - } - } - if (uniqueSigners.size() >= reqSignerCnt.get().intValue()){ - recvMessage(srcNetwork, _connSn, msg); - } - } + @External + public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, + byte[][] signatures) { + OnlyAdmin(); + Context.require(signatures.length >= reqSignerCnt.get().intValue(), "Not enough signatures"); + List
uniqueSigners = new ArrayList<>(); + for (byte[] signature : signatures) { + Address signer = getSigner(msg, signature); + Context.require(signerExists(signer), "Invalid signature provided"); + if (!uniqueSigners.contains(signer)) { + uniqueSigners.add(signer); + } + } + if (uniqueSigners.size() >= reqSignerCnt.get().intValue()) { + recvMessage(srcNetwork, _connSn, msg); + } + } private boolean signerExists(Address signer) { for (int i = 0; i < signers.size(); i++) { diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 77cd6396..6fa7dd38 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -75,20 +75,11 @@ public class ClusterConnectionTest extends TestBase { protected MockContract callservice; - // @BeforeAll - // protected static void init() { - // contextMock = Mockito.mockStatic(Context.class, Mockito.CALLS_REAL_METHODS); - // } - @BeforeEach public void setup() throws Exception { Security.addProvider(new BouncyCastleProvider()); callservice = new MockContract<>(CallServiceScoreInterface.class, CallService.class, sm, owner); - // xcall = sm.deploy(owner, CallService.class, nidSource); - // xcallSpy = (CallService) spy(xcall.getInstance()); - // xcall.setInstance(xcallSpy); - // contextMock.reset(); connection = sm.deploy(owner, ClusterConnection.class, source_relayer.getAddress(), callservice.getAddress()); @@ -98,8 +89,6 @@ public void setup() throws Exception { @Test public void testSetAdmin() { - // connection.invoke(source_relayer, "setFee", "0xevm", BigInteger.TEN, - // BigInteger.TEN); connection.invoke(source_relayer, "setAdmin", admin.getAddress()); assertEquals(connection.call("admin"), admin.getAddress()); @@ -256,12 +245,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byteArray[0] = wallet.sign(messageHash); connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); - connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); + assertEquals("Reverted(0): Not enough signatures", e.getMessage()); verifyNoInteractions(callservice.mock); } - // Hash the message with Keccak-256 public static byte[] keccak256(byte[] input) { Keccak.Digest256 keccak256 = new Keccak.Digest256(); return keccak256.digest(input); From e851994872eb7e9badef34bc09a5d51de707635c Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 11:15:28 +0545 Subject: [PATCH 17/75] chore: code refactorings --- .../adapter/cluster/ClusterConnection.java | 90 +++++++++---------- .../cluster/ClusterConnectionTest.java | 32 +++---- 2 files changed, 61 insertions(+), 61 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 51d1ee4f..0044e441 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -36,9 +36,9 @@ public class ClusterConnection { protected final VarDB
xCall = Context.newVarDB("callService", Address.class); protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); - protected final VarDB reqSignerCnt = Context.newVarDB("reqSignerCnt", BigInteger.class); + protected final VarDB reqValidatorCnt = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); - private final ArrayDB
signers = Context.newArrayDB("signers", Address.class); + private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); @@ -50,49 +50,49 @@ public ClusterConnection(Address _relayer, Address _xCall) { xCall.set(_xCall); adminAddress.set(_relayer); connSn.set(BigInteger.ZERO); - signers.add(_relayer); - SignerAdded(_relayer); + validators.add(_relayer); + ValidatorAdded(_relayer); } } /** - * Retrieves the signers. + * Retrieves the validators. * - * @return The signers . + * @return The validators . */ @External(readonly = true) - public Address[] listSigners() { - Address[] sgs = new Address[signers.size()]; - for(int i=0; i < signers.size(); i++) { - sgs[i] = signers.get(i); + public Address[] listValidators() { + Address[] sgs = new Address[validators.size()]; + for(int i = 0; i < validators.size(); i++) { + sgs[i] = validators.get(i); } return sgs; } @External - public void addSigner(Address signer) { + public void addValidator(Address _validator) { OnlyAdmin(); - if (!signerExists(signer)){ - signers.add(signer); - SignerAdded(signer); + if (!validatorExists(_validator)){ + validators.add(_validator); + ValidatorAdded(_validator); } } @External - public void removeSigner(Address _signer) { + public void removeValidator(Address _validator) { OnlyAdmin(); - Context.require(_signer != adminAddress.get(),"cannot remove admin"); - if (signerExists(_signer)){ - Address top = this.signers.pop(); - if (!top.equals(_signer)) { - for (int i = 0; i < this.signers.size(); i++) { - if (_signer.equals(this.signers.get(i))) { - this.signers.set(i, top); + Context.require(_validator != adminAddress.get(),"cannot remove admin"); + if (validatorExists(_validator)){ + Address top = this.validators.pop(); + if (!top.equals(_validator)) { + for (int i = 0; i < this.validators.size(); i++) { + if (_validator.equals(this.validators.get(i))) { + this.validators.set(i, top); break; } } - } - SignerRemoved(_signer); + } + ValidatorRemoved(_validator); } } @@ -101,11 +101,11 @@ public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } @EventLog(indexed = 1) - public void SignerAdded(Address _signer) { + public void ValidatorAdded(Address _validator) { } @EventLog(indexed = 1) - public void SignerRemoved(Address _signer) { + public void ValidatorRemoved(Address _validator) { } /** @@ -130,24 +130,24 @@ public Address admin() { } /** - * Sets the required signer count + * Sets the required validator count * - * @param _signerCnt the new required signer count + * @param _validatorCnt the new required validator count */ @External - public void setRequiredSignerCount(BigInteger _signerCnt) { + public void setRequiredValidatorCount(BigInteger _validatorCnt) { OnlyAdmin(); - reqSignerCnt.set(_signerCnt); + reqValidatorCnt.set(_validatorCnt); } /** - * Retrieves the required signer count. + * Retrieves the required validator count. * - * @return The required signer count. + * @return The required validator count. */ @External(readonly = true) - public BigInteger requiredSignerCount() { - return reqSignerCnt.get(); + public BigInteger requiredValidatorCount() { + return reqValidatorCnt.get(); } /** @@ -220,23 +220,23 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, byte[][] signatures) { OnlyAdmin(); - Context.require(signatures.length >= reqSignerCnt.get().intValue(), "Not enough signatures"); - List
uniqueSigners = new ArrayList<>(); + Context.require(signatures.length >= reqValidatorCnt.get().intValue(), "Not enough signatures"); + List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address signer = getSigner(msg, signature); - Context.require(signerExists(signer), "Invalid signature provided"); - if (!uniqueSigners.contains(signer)) { - uniqueSigners.add(signer); + Address validator = getValidator(msg, signature); + Context.require(validatorExists(validator), "Invalid signature provided"); + if (!uniqueValidators.contains(validator)) { + uniqueValidators.add(validator); } } - if (uniqueSigners.size() >= reqSignerCnt.get().intValue()) { + if (uniqueValidators.size() >= reqValidatorCnt.get().intValue()) { recvMessage(srcNetwork, _connSn, msg); } } - private boolean signerExists(Address signer) { - for (int i = 0; i < signers.size(); i++) { - if (signers.get(i).equals(signer)) { + private boolean validatorExists(Address _validator) { + for (int i = 0; i < validators.size(); i++) { + if (validators.get(i).equals(_validator)) { return true; } } @@ -258,7 +258,7 @@ public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } - private Address getSigner(byte[] msg,byte[] sig){ + private Address getValidator(byte[] msg, byte[] sig){ byte[] hashMessage = getHash(msg); byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); return Context.getAddressFromKey(key); diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 6fa7dd38..61438ad1 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -214,8 +214,8 @@ public void testRecvMessageWithSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.ONE); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -229,9 +229,9 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -243,8 +243,8 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredSignerCount", BigInteger.TWO); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -260,8 +260,8 @@ public static byte[] keccak256(byte[] input) { @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - Address[] signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } @@ -270,17 +270,17 @@ public void testAddNRemoveSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); KeyWallet wallet3 = KeyWallet.create(); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addSigner", Address.fromString(wallet2.getAddress().toString())); - Address[] signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 3); - connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet3.getAddress().toString())); - signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString())); + signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 3); - connection.invoke(source_relayer, "removeSigner", Address.fromString(wallet2.getAddress().toString())); - signers = connection.call(Address[].class,"listSigners"); + connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet2.getAddress().toString())); + signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 038b88cb8f0dfe449c344d457dc2977ec5d860fa Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Thu, 26 Sep 2024 11:24:07 +0545 Subject: [PATCH 18/75] chore: code refactorings --- .../contracts/adapters/ClusterConnection.sol | 61 +++++++++---------- .../evm/test/adapters/ClusterConnection.t.sol | 36 +++++------ 2 files changed, 48 insertions(+), 49 deletions(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index 22c0f290..f0805e9c 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -11,17 +11,17 @@ contract ClusterConnection is Initializable, IConnection { mapping(string => uint256) private messageFees; mapping(string => uint256) private responseFees; mapping(string => mapping(uint256 => bool)) receipts; - mapping(address => bool) public isSigner; + mapping(address => bool) public isValidator; address private xCall; address private adminAddress; uint256 public connSn; - address[] private signers; - uint8 private reqSigCount; + address[] private validators; + uint8 private reqValidatorCnt; event Message(string targetNetwork, uint256 sn, bytes _msg); - event SignerAdded(address _signer); - event SignerRemoved(address _signer); + event ValidatorAdded(address _validator); + event ValidatorRemoved(address _validator); modifier onlyAdmin() { require(msg.sender == this.admin(), "OnlyRelayer"); @@ -30,33 +30,32 @@ contract ClusterConnection is Initializable, IConnection { function initialize(address _relayer, address _xCall) public initializer { xCall = _xCall; - signers.push(_relayer); + validators.push(_relayer); adminAddress = _relayer; - emit SignerAdded(_relayer); + emit ValidatorAdded(_relayer); } - function listSigners() external view returns (address[] memory) { - return signers; + function listValidators() external view returns (address[] memory) { + return validators; } - function addSigner(address _newSigner) external onlyAdmin { - require(!isSigner[_newSigner], "Address is already an signer"); - signers.push(_newSigner); - isSigner[_newSigner] = true; - emit SignerAdded(_newSigner); + function addValidator(address _validator) external onlyAdmin { + require(!isValidator[_validator], "Address is already an signer"); + validators.push(_validator); + isValidator[_validator] = true; + emit ValidatorAdded(_validator); } - // Function to remove an existing signer - function removeSigner(address _signer) external onlyAdmin { - require(_signer!=this.admin(), "cannot remove admin"); - for (uint i = 0; i < signers.length; i++) { - if (signers[i] == _signer) { - signers[i] = signers[signers.length - 1]; - signers.pop(); - isSigner[_signer] = false; + function removeValidator(address _validator) external onlyAdmin { + require(_validator!=this.admin(), "cannot remove admin"); + for (uint i = 0; i < validators.length; i++) { + if (validators[i] == _validator) { + validators[i] = validators[validators.length - 1]; + validators.pop(); + isValidator[_validator] = false; break; } - emit SignerRemoved(_signer); + emit ValidatorRemoved(_validator); } } @@ -132,25 +131,25 @@ contract ClusterConnection is Initializable, IConnection { bytes[] calldata _signedMessages ) public onlyAdmin { require(_signedMessages.length > 0, "No signatures provided"); - require(_signedMessages.length >= reqSigCount, "Not enough signatures passed"); + require(_signedMessages.length >= reqValidatorCnt, "Not enough signatures passed"); bytes32 messageHash = keccak256(_msg); uint signerCount = 0; address[] memory collectedSigners = new address[](_signedMessages.length); for (uint i = 0; i < _signedMessages.length; i++) { address signer = recoverSigner(messageHash, _signedMessages[i]); require(signer != address(0), "Invalid signature"); - if (!isSignerProcessed(collectedSigners, signer)){ + if (!isValidatorProcessed(collectedSigners, signer)){ collectedSigners[signerCount] = signer; signerCount++; } } - if (signerCount >= reqSigCount) { + if (signerCount >= reqValidatorCnt) { recvMessage(srcNetwork,_connSn,_msg); } } - function isSignerProcessed(address[] memory processedSigners, address signer) public pure returns (bool) { + function isValidatorProcessed(address[] memory processedSigners, address signer) public pure returns (bool) { for (uint i = 0; i < processedSigners.length; i++) { if (processedSigners[i] == signer) { return true; @@ -246,11 +245,11 @@ contract ClusterConnection is Initializable, IConnection { @notice Set the required signature count for verification. @param _count The desired count. */ - function setRequiredCount(uint8 _count) external onlyAdmin { - reqSigCount = _count; + function setRequiredValidatorCount(uint8 _count) external onlyAdmin { + reqValidatorCnt = _count; } - function getRequiredCount() external view returns (uint8) { - return reqSigCount; + function getRequiredValidatorCount() external view returns (uint8) { + return reqValidatorCnt; } } diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 0251ef4a..46e35e83 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -337,11 +337,11 @@ contract ClusterConnectionTest is Test { uint256 pk4 = hexStringToUint256("2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"); bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); vm.startPrank(destination_relayer); - adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addSigner(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); - adapterTarget.addSigner(address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8)); - adapterTarget.addSigner(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); - adapterTarget.setRequiredCount(4); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addValidator(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); + adapterTarget.addValidator(address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8)); + adapterTarget.addValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); + adapterTarget.setRequiredValidatorCount(4); vm.expectEmit(); emit CallMessage(iconDapp, ParseAddress.toString(address(dappSource)), 1, 1, data); vm.expectCall(address(xCallTarget), abi.encodeCall(xCallTarget.handleMessage, (nidSource,RLPEncodeStruct.encodeCSMessage(message)))); @@ -390,29 +390,29 @@ contract ClusterConnectionTest is Test { return number; } - function testAddSigner() public { + function testAddValidator() public { vm.startPrank(destination_relayer); - adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - assertEq(2, adapterTarget.listSigners().length); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + assertEq(2, adapterTarget.listValidators().length); vm.stopPrank(); } - function testRemoveSigner() public { + function testRemoveValidator() public { vm.startPrank(destination_relayer); - adapterTarget.addSigner(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addSigner(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - assertEq(3, adapterTarget.listSigners().length); - adapterTarget.removeSigner(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); - assertEq(3, adapterTarget.listSigners().length); - adapterTarget.removeSigner(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - assertEq(2, adapterTarget.listSigners().length); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(3, adapterTarget.listValidators().length); + adapterTarget.removeValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); + assertEq(3, adapterTarget.listValidators().length); + adapterTarget.removeValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(2, adapterTarget.listValidators().length); vm.stopPrank(); } function testRequiredCount() public { vm.startPrank(destination_relayer); - adapterTarget.setRequiredCount(3); - assertEq(3, adapterTarget.getRequiredCount()); + adapterTarget.setRequiredValidatorCount(3); + assertEq(3, adapterTarget.getRequiredValidatorCount()); vm.stopPrank(); } } From 7edb48b1a50029ea44a34220b9e1f875a21e9986 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 27 Sep 2024 14:22:07 +0545 Subject: [PATCH 19/75] feat: add relayers add and remove functionaility --- .../main/java/relay/aggregator/HexString.java | 26 --- .../main/java/relay/aggregator/Packet.java | 37 +++- .../relay/aggregator/RelayAggregator.java | 160 +++++++++++++-- .../relay/aggregator/RelayAggregatorTest.java | 184 ++++++++++++++++-- 4 files changed, 341 insertions(+), 66 deletions(-) delete mode 100644 contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java deleted file mode 100644 index 6c12e466..00000000 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/HexString.java +++ /dev/null @@ -1,26 +0,0 @@ -package relay.aggregator; - -public class HexString { - public static byte[] toBytes(String hex) { - int len = hex.length(); - byte[] data = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) - + Character.digit(hex.charAt(i + 1), 16)); - } - return data; - } - - public static String fromBytes(byte[] bytes) { - StringBuilder hexString = new StringBuilder(); - for (byte b : bytes) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); // Append leading zero if needed - } - hexString.append(hex); - } - return hexString.toString(); - } -} 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 64c6b20a..e481a235 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.Context; import score.ObjectReader; import score.ObjectWriter; @@ -22,6 +23,11 @@ public class Packet { */ private final BigInteger srcSn; + /** + * The source height of the packet in the source network (chain). + */ + private final BigInteger srcHeight; + /** * The ID of the destination network (chain) where the packet is being sent. */ @@ -42,20 +48,24 @@ public class Packet { * @param data the payload data for this packet. * @throws IllegalArgumentException if {@code srcNetwork}, * {@code contractAddress}, {@code srcSn}, + * {@code srcHeight}, * {@code dstNetwork}, or {@code data} is * {@code null}. */ - public Packet(String srcNetwork, String contractAddress, BigInteger srcSn, String dstNetwork, byte[] data) { - if (srcNetwork == null || contractAddress == null || contractAddress == null || srcSn == null - || dstNetwork == null || data == null) { - throw new IllegalArgumentException( - "srcNetwork, contractAddress, srcSn, dstNetwork, and data cannot be null"); + public Packet(String srcNetwork, String contractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, + byte[] data) { + Boolean isIllegalArg = srcNetwork == null || contractAddress == null || contractAddress == null || srcSn == null + || srcHeight == null || dstNetwork == null || data == null; + Context.require(!isIllegalArg, + "srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, and data cannot be null"); + if (isIllegalArg) { } this.srcNetwork = srcNetwork; this.contractAddress = contractAddress; this.srcSn = srcSn; + this.srcHeight = srcHeight; this.dstNetwork = dstNetwork; - this.data = data.clone(); // clone for immutability + this.data = data; } public String getId() { @@ -93,6 +103,15 @@ public BigInteger getSrcSn() { return srcSn; } + /** + * Returns the height of the packet in the source network (chain). + * + * @return the source height. + */ + public BigInteger getSrcHeight() { + return srcHeight; + } + /** * Returns the destination network (chain) where the packet is being sent. * @@ -108,14 +127,15 @@ public String getDstNetwork() { * @return a byte array containing the packet data. */ public byte[] getData() { - return data.clone(); // return a clone to preserve immutability + return data; } public static void writeObject(ObjectWriter w, Packet p) { - w.beginList(5); + w.beginList(6); w.write(p.srcNetwork); w.write(p.contractAddress); w.write(p.srcSn); + w.write(p.srcHeight); w.write(p.dstNetwork); w.writeNullable(p.data); w.end(); @@ -127,6 +147,7 @@ public static Packet readObject(ObjectReader r) { r.readString(), r.readString(), r.readBigInteger(), + r.readBigInteger(), r.readString(), r.readNullable(byte[].class)); r.end(); 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 bacf3b0f..a4e5ed15 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -24,11 +24,18 @@ import score.VarDB; import score.DictDB; import score.BranchDB; +import score.ByteArrayObjectWriter; import score.annotation.EventLog; import score.annotation.External; +import score.ObjectReader; import scorex.util.ArrayList; +import scorex.util.HashMap; public class RelayAggregator { + private final Integer DEFAULT_SIGNATURE_THRESHOLD = 2; + + private final VarDB signatureThreshold = Context.newVarDB("signatureThreshold", Integer.class); + private final VarDB
admin = Context.newVarDB("admin", Address.class); private final ArrayDB
relayers = Context.newArrayDB("relayers", Address.class); @@ -44,6 +51,7 @@ public RelayAggregator(Address _admin, Address[] _relayers) { for (Address relayer : _relayers) { relayers.add(relayer); } + signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD); } } @@ -58,28 +66,100 @@ public Address getAdmin() { return admin.get(); } + @External + public void setSignatureThreshold(int threshold) { + adminOnly(); + signatureThreshold.set(threshold); + } + + @External(readonly = true) + public Integer getSignatureThreshold() { + return signatureThreshold.get(); + } + + @External(readonly = true) + public Address[] getRelayers() { + Address[] rlrs = new Address[relayers.size()]; + for (int i = 0; i < relayers.size(); i++) { + rlrs[i] = relayers.get(i); + } + return rlrs; + } + + @External + public void addRelayers(Address[] newRelayers) { + adminOnly(); + + Context.require(newRelayers != null && newRelayers.length != 0, "new relayers cannot be empty"); + + HashMap existingRelayers = new HashMap(); + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + existingRelayers.put(relayer, true); + } + + for (Address newRelayer : newRelayers) { + if (!existingRelayers.containsKey(newRelayer)) { + relayers.add(newRelayer); + existingRelayers.put(newRelayer, true); + } + } + } + + @External + public void removeRelayers(Address[] relayersToBeRemoved) { + adminOnly(); + + Context.require(relayersToBeRemoved != null && relayersToBeRemoved.length != 0, + "relayers to be removed cannot be empty"); + + HashMap existingRelayers = new HashMap(); + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + existingRelayers.put(relayer, i); + } + + for (Address relayerToBeRemoved : relayersToBeRemoved) { + if (existingRelayers.containsKey(relayerToBeRemoved)) { + Address top = relayers.pop(); + if (!top.equals(relayerToBeRemoved)) { + Integer pos = existingRelayers.get(relayerToBeRemoved); + relayers.set(pos, top); + } + existingRelayers.remove(relayerToBeRemoved); + } + } + } + @External public void registerPacket( String srcNetwork, String contractAddress, BigInteger srcSn, + BigInteger srcHeight, String dstNetwork, byte[] data) { adminOnly(); - Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, dstNetwork, data); + Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); String id = pkt.getId(); Context.require(packets.get(id) == null, "Packet already exists"); packets.set(id, pkt); - PacketRegistered(pkt.getSrcNetwork(), pkt.getContractAddress(), pkt.getSrcSn()); + PacketRegistered( + pkt.getSrcNetwork(), + pkt.getContractAddress(), + pkt.getSrcSn(), + pkt.getSrcHeight(), + pkt.getDstNetwork(), + pkt.getData()); } @External - public void submitSignature( + public void acknowledgePacket( String srcNetwork, String contractAddress, BigInteger srcSn, @@ -97,17 +177,21 @@ public void submitSignature( setSignature(pktID, Context.getCaller(), signature); if (signatureThresholdReached(pktID)) { - PacketConfirmed( + byte[][] sigs = getSignatures(srcNetwork, contractAddress, srcSn); + byte[] encodedSigs = serializeSignatures(sigs); + PacketAcknowledged( pkt.getSrcNetwork(), pkt.getContractAddress(), pkt.getSrcSn(), + pkt.getSrcHeight(), pkt.getDstNetwork(), - pkt.getData()); + pkt.getData(), + encodedSigs); + removePacket(pktID); } } - @External(readonly = true) - public ArrayList getSignatures(String srcNetwork, String contractAddress, BigInteger srcSn) { + private byte[][] getSignatures(String srcNetwork, String contractAddress, BigInteger srcSn) { String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); DictDB signDict = signatures.at(pktID); ArrayList signatureList = new ArrayList(); @@ -119,13 +203,49 @@ public ArrayList getSignatures(String srcNetwork, String contractAddress signatureList.add(sign); } } - return signatureList; + + byte[][] sigs = new byte[signatureList.size()][]; + for (int i = 0; i < signatureList.size(); i++) { + sigs[i] = signatureList.get(i); + } + return sigs; } protected void setSignature(String pktID, Address addr, byte[] sign) { signatures.at(pktID).set(addr, sign); } + protected static byte[] serializeSignatures(byte[][] sigs) { + ByteArrayObjectWriter w = Context.newByteArrayObjectWriter("RLPn"); + w.beginList(sigs.length); + + for (byte[] sig : sigs) { + w.write(sig); + } + + w.end(); + return w.toByteArray(); + } + + protected static byte[][] deserializeSignatures(byte[] encodedSigs) { + ObjectReader r = Context.newByteArrayObjectReader("RLPn", encodedSigs); + + ArrayList sigList = new ArrayList<>(); + + r.beginList(); + while (r.hasNext()) { + sigList.add(r.readByteArray()); + } + r.end(); + + byte[][] sigs = new byte[sigList.size()][]; + for (int i = 0; i < sigList.size(); i++) { + sigs[i] = sigList.get(i); + } + + return sigs; + } + private void adminOnly() { Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the leader relayer"); } @@ -152,23 +272,37 @@ private Boolean signatureThresholdReached(String pktID) { noOfSignatures++; } } - int threshold = (relayers.size() * 66) / 100; - return noOfSignatures >= threshold; + return noOfSignatures >= 2; + } + + private void removePacket(String pktID) { + packets.set(pktID, null); + DictDB signDict = signatures.at(pktID); + + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + signDict.set(relayer, null); + } } @EventLog(indexed = 2) public void PacketRegistered( String srcNetwork, String contractAddress, - BigInteger srcSn) { + BigInteger srcSn, + BigInteger srcHeight, + String dstNetwork, + byte[] data) { } @EventLog(indexed = 2) - public void PacketConfirmed( + public void PacketAcknowledged( String srcNetwork, String contractAddress, BigInteger srcSn, + BigInteger srcHeight, String dstNetwork, - byte[] data) { + byte[] data, + byte[] signatures) { } } \ No newline at end of file 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 d3369644..b01113d0 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -2,6 +2,7 @@ import java.math.BigInteger; +import org.bouncycastle.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -14,10 +15,12 @@ import com.iconloop.score.test.Score; import com.iconloop.score.test.ServiceManager; import com.iconloop.score.test.TestBase; + import foundation.icon.icx.KeyWallet; import static org.junit.jupiter.api.Assertions.assertEquals; 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; @@ -36,6 +39,9 @@ class RelayAggregatorTest extends TestBase { private KeyWallet relayerThree; private Account relayerThreeAc; + private KeyWallet relayerFour; + private Account relayerFourAc; + private Score aggregator; private RelayAggregator aggregatorSpy; @@ -53,7 +59,11 @@ void setup() throws Exception { relayerThree = KeyWallet.create(); relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); - Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress() }; + relayerFour = KeyWallet.create(); + relayerFourAc = sm.getAccount(Address.fromString(relayerFour.getAddress().toString())); + + Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress(), + relayerThreeAc.getAddress() }; aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); @@ -80,17 +90,113 @@ public void testSetAdmin_unauthorized() { assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); } + @Test + public void testSetSignatureThreshold() { + int threshold = 3; + aggregator.invoke(adminAc, "setSignatureThreshold", threshold); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); + } + + @Test + public void testSetSignatureThreshold_unauthorised() { + int threshold = 3; + + Executable action = () -> aggregator.invoke(relayerOneAc, "setSignatureThreshold", threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + } + + @Test + public void testAddRelayers() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerFourAc.getAddress(), relayerFiveAc.getAddress() }; + + aggregator.invoke(adminAc, "addRelayers", (Object) newRelayers); + + Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); + + assertTrue(updatedRelayers[updatedRelayers.length - 1].equals(relayerFiveAc.getAddress())); + assertTrue(updatedRelayers[updatedRelayers.length - 2].equals(relayerFourAc.getAddress())); + } + + @Test + public void testAddRelayers_unauthorised() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerFourAc.getAddress(), relayerFiveAc.getAddress() }; + + Executable action = () -> aggregator.invoke(relayerOneAc, "addRelayers", (Object) newRelayers); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + } + + @Test + public void testRemoveRelayers() { + Address[] relayerToBeRemoved = new Address[] { relayerOneAc.getAddress(), + relayerTwoAc.getAddress() }; + + aggregator.invoke(adminAc, "removeRelayers", (Object) relayerToBeRemoved); + + Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); + + Boolean removed = true; + for (Address rlr : updatedRelayers) { + if (rlr.equals(relayerOneAc.getAddress()) || rlr.equals(relayerTwoAc.getAddress())) { + removed = false; + break; + } + } + + assertTrue(removed); + assertEquals(updatedRelayers[0], relayerThreeAc.getAddress()); + } + + @Test + public void testRemoveRelayers_unauthorised() { + Address[] relayerToBeRemoved = new Address[] { relayerOneAc.getAddress(), + relayerTwoAc.getAddress() }; + + aggregator.invoke(adminAc, "removeRelayers", (Object) relayerToBeRemoved); + + Executable action = () -> aggregator.invoke(relayerFourAc, "removeRelayers", (Object) relayerToBeRemoved); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + } + @Test public void testRegisterPacket() { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn); + verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); + } + + @Test + public void testRegisterPacket_nullArg() { + String srcNetwork = null; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + Executable action = () -> aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, + srcHeight, dstNetwork, data); + + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, and data cannot be null", + e.getMessage()); } @Test @@ -98,81 +204,121 @@ public void testRegisterPacket_duplicate() { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); Executable action = () -> aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, - dstNetwork, data); + srcHeight, dstNetwork, data); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Packet already exists", e.getMessage()); } @Test - public void testSubmitSignature() throws Exception { + public void testAcknowledgePacket() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, sign); + aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, sign); String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); } @Test - public void testSubmitSignature_unauthorized() throws Exception { + public void testAcknowledgePacket_thresholdReached() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); + + byte[] dataHash = Context.hash("sha-256", data); + + byte[] signOne = relayerOne.sign(dataHash); + aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, signOne); + + byte[] signTwo = relayerTwo.sign(dataHash); + aggregator.invoke(relayerTwoAc, "acknowledgePacket", srcNetwork, + contractAddress, srcSn, signTwo); + + byte[][] sigs = new byte[2][]; + sigs[0] = signOne; + sigs[1] = signTwo; + + byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); + byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); + + assertTrue(Arrays.areEqual(signOne, decodedSigs[0])); + assertTrue(Arrays.areEqual(signTwo, decodedSigs[1])); + + verify(aggregatorSpy).PacketAcknowledged(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + encodedSigs); + } + + @Test + public void testAcknowledgePacket_unauthorized() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerThree.sign(dataHash); + byte[] sign = relayerFour.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerThreeAc, "submitSignature", srcNetwork, contractAddress, + Executable action = () -> aggregator.invoke(relayerFourAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, sign); + UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", e.getMessage()); } @Test - public void testSubmitSignature_duplicate() throws Exception { + public void testAcknowledgePacket_duplicate() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, dstNetwork, data); + aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, sign); + aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, sign); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, + Executable action = () -> aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, + srcSn, sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Signature already exists", e.getMessage()); } @Test - public void testSubmitSignature_packetUnregistered() throws Exception { + public void testAcknowledgePacket_packetUnregistered() throws Exception { String srcNetwork = "0x2.icon"; BigInteger srcSn = BigInteger.ONE; String contractAddress = "hxjuiod"; @@ -181,10 +327,10 @@ public void testSubmitSignature_packetUnregistered() throws Exception { byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitSignature", srcNetwork, contractAddress, srcSn, + Executable action = () -> aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, + srcSn, sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Packet not registered", e.getMessage()); } - } From 34b8f2219660a7062dac7f57e2246aa74a6c0360 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 27 Sep 2024 15:02:51 +0545 Subject: [PATCH 20/75] fix: remove unwanted deps --- contracts/javascore/aggregator/build.gradle | 5 ----- .../src/test/java/relay/aggregator/RelayAggregatorTest.java | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle index 0704d410..f8881dcc 100644 --- a/contracts/javascore/aggregator/build.gradle +++ b/contracts/javascore/aggregator/build.gradle @@ -1,11 +1,6 @@ version = '0.1.0' -repositories { - mavenCentral() -} - dependencies { - implementation 'org.bouncycastle:bcprov-jdk15on:1.70' testImplementation 'foundation.icon:javaee-unittest:0.11.1' testImplementation project(':test-lib') } 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 b01113d0..8e8d6ee1 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -2,7 +2,6 @@ import java.math.BigInteger; -import org.bouncycastle.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; @@ -18,6 +17,7 @@ import foundation.icon.icx.KeyWallet; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -264,8 +264,8 @@ public void testAcknowledgePacket_thresholdReached() throws Exception { byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); - assertTrue(Arrays.areEqual(signOne, decodedSigs[0])); - assertTrue(Arrays.areEqual(signTwo, decodedSigs[1])); + assertArrayEquals(signOne, decodedSigs[0]); + assertArrayEquals(signTwo, decodedSigs[1]); verify(aggregatorSpy).PacketAcknowledged(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, encodedSigs); From ef5d937ec9c01b0f77c718d0821b3c9f264c4e5f Mon Sep 17 00:00:00 2001 From: sherpalden Date: Mon, 30 Sep 2024 14:22:06 +0545 Subject: [PATCH 21/75] fix: use primitive int for external methods --- contracts/javascore/aggregator/build.gradle | 3 +-- .../src/main/java/relay/aggregator/RelayAggregator.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle index f8881dcc..b4c26462 100644 --- a/contracts/javascore/aggregator/build.gradle +++ b/contracts/javascore/aggregator/build.gradle @@ -37,7 +37,6 @@ deployJar { password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' parameters { arg('_admin', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") - arg('_relayers', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") - arg('_relayers', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fe") + arg('_relayers', "[]") } } \ No newline at end of file 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 a4e5ed15..c0bd8801 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -73,7 +73,7 @@ public void setSignatureThreshold(int threshold) { } @External(readonly = true) - public Integer getSignatureThreshold() { + public int getSignatureThreshold() { return signatureThreshold.get(); } @@ -272,7 +272,7 @@ private Boolean signatureThresholdReached(String pktID) { noOfSignatures++; } } - return noOfSignatures >= 2; + return noOfSignatures >= signatureThreshold.get(); } private void removePacket(String pktID) { From 29e17dd56d406c559498d7a1b14d3b8b4dc94381 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Mon, 30 Sep 2024 15:02:09 +0545 Subject: [PATCH 22/75] fix: make relayers list configurable from setter function --- contracts/javascore/aggregator/build.gradle | 1 - .../src/main/java/relay/aggregator/RelayAggregator.java | 5 +---- .../src/test/java/relay/aggregator/RelayAggregatorTest.java | 4 +++- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle index b4c26462..426026ca 100644 --- a/contracts/javascore/aggregator/build.gradle +++ b/contracts/javascore/aggregator/build.gradle @@ -37,6 +37,5 @@ deployJar { password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' parameters { arg('_admin', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") - arg('_relayers', "[]") } } \ No newline at end of file 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 c0bd8801..784bd9c4 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -45,12 +45,9 @@ public class RelayAggregator { private final BranchDB> signatures = Context.newBranchDB("signatures", byte[].class); - public RelayAggregator(Address _admin, Address[] _relayers) { + public RelayAggregator(Address _admin) { if (admin.get() == null) { admin.set(_admin); - for (Address relayer : _relayers) { - relayers.add(relayer); - } signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD); } } 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 8e8d6ee1..1a2e798b 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -62,9 +62,11 @@ void setup() throws Exception { relayerFour = KeyWallet.create(); relayerFourAc = sm.getAccount(Address.fromString(relayerFour.getAddress().toString())); + aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress()); + Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress(), relayerThreeAc.getAddress() }; - aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress(), relayers); + aggregator.invoke(adminAc, "addRelayers", (Object) relayers); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); aggregator.setInstance(aggregatorSpy); From 8e8c581c17a42658b6d7a26b48c85bb5bff6d816 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Wed, 2 Oct 2024 21:30:59 +0545 Subject: [PATCH 23/75] refactor: add lookup for relayers --- .../relay/aggregator/RelayAggregator.java | 24 ++++++------------- 1 file changed, 7 insertions(+), 17 deletions(-) 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 784bd9c4..416786d3 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -39,6 +39,7 @@ public class RelayAggregator { private final VarDB
admin = Context.newVarDB("admin", Address.class); 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); @@ -89,16 +90,11 @@ public void addRelayers(Address[] newRelayers) { Context.require(newRelayers != null && newRelayers.length != 0, "new relayers cannot be empty"); - HashMap existingRelayers = new HashMap(); - for (int i = 0; i < relayers.size(); i++) { - Address relayer = relayers.get(i); - existingRelayers.put(relayer, true); - } - for (Address newRelayer : newRelayers) { - if (!existingRelayers.containsKey(newRelayer)) { + Boolean exits = relayersLookup.get(newRelayer); + if (exits == null) { relayers.add(newRelayer); - existingRelayers.put(newRelayer, true); + relayersLookup.set(newRelayer, true); } } } @@ -118,6 +114,7 @@ public void removeRelayers(Address[] relayersToBeRemoved) { for (Address relayerToBeRemoved : relayersToBeRemoved) { if (existingRelayers.containsKey(relayerToBeRemoved)) { + relayersLookup.set(relayerToBeRemoved, null); Address top = relayers.pop(); if (!top.equals(relayerToBeRemoved)) { Integer pos = existingRelayers.get(relayerToBeRemoved); @@ -249,15 +246,8 @@ private void adminOnly() { private void relayersOnly() { Address caller = Context.getCaller(); - Boolean isRelayer = false; - for (int i = 0; i < relayers.size(); i++) { - Address relayer = relayers.get(i); - if (relayer.equals(caller)) { - isRelayer = true; - break; - } - } - Context.require(isRelayer, "Unauthorized: caller is not a registered relayer"); + Boolean isRelayer = relayersLookup.get(caller); + Context.require(isRelayer != null && isRelayer, "Unauthorized: caller is not a registered relayer"); } private Boolean signatureThresholdReached(String pktID) { From 555f5eb2bd46087753762faf84838c8a209c9e55 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 4 Oct 2024 12:55:47 +0545 Subject: [PATCH 24/75] fix: handle packet registration and signature submission in a single method --- .../relay/aggregator/RelayAggregator.java | 44 +++--- .../relay/aggregator/RelayAggregatorTest.java | 126 ++++-------------- 2 files changed, 43 insertions(+), 127 deletions(-) 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 416786d3..fd3a8ccd 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -126,44 +126,30 @@ public void removeRelayers(Address[] relayersToBeRemoved) { } @External - public void registerPacket( + public void submitPacket( String srcNetwork, String contractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, - byte[] data) { - - adminOnly(); - - Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - String id = pkt.getId(); - - Context.require(packets.get(id) == null, "Packet already exists"); - - packets.set(id, pkt); - - PacketRegistered( - pkt.getSrcNetwork(), - pkt.getContractAddress(), - pkt.getSrcSn(), - pkt.getSrcHeight(), - pkt.getDstNetwork(), - pkt.getData()); - } - - @External - public void acknowledgePacket( - String srcNetwork, - String contractAddress, - BigInteger srcSn, + byte[] data, byte[] signature) { relayersOnly(); - String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); - Packet pkt = packets.get(pktID); - Context.require(pkt != null, "Packet not registered"); + Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); + String pktID = pkt.getId(); + + if (packets.get(pktID) == null) { + packets.set(pktID, pkt); + PacketRegistered( + pkt.getSrcNetwork(), + pkt.getContractAddress(), + pkt.getSrcSn(), + pkt.getSrcHeight(), + pkt.getDstNetwork(), + pkt.getData()); + } byte[] existingSign = signatures.at(pktID).get(Context.getCaller()); Context.require(existingSign == null, "Signature already exists"); 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 1a2e798b..d043ea0d 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -64,8 +64,9 @@ void setup() throws Exception { aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress()); - Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress(), + Address[] relayers = new Address[] { adminAc.getAddress(), relayerOneAc.getAddress(), relayerTwoAc.getAddress(), relayerThreeAc.getAddress() }; + aggregator.invoke(adminAc, "addRelayers", (Object) relayers); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); @@ -153,7 +154,7 @@ public void testRemoveRelayers() { } assertTrue(removed); - assertEquals(updatedRelayers[0], relayerThreeAc.getAddress()); + assertEquals(updatedRelayers[1], relayerThreeAc.getAddress()); } @Test @@ -170,57 +171,7 @@ public void testRemoveRelayers_unauthorised() { } @Test - public void testRegisterPacket() { - String srcNetwork = "0x2.icon"; - String dstNetwork = "sui"; - BigInteger srcSn = BigInteger.ONE; - BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; - byte[] data = new byte[] { 0x01, 0x02 }; - - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - - verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - } - - @Test - public void testRegisterPacket_nullArg() { - String srcNetwork = null; - String dstNetwork = "sui"; - BigInteger srcSn = BigInteger.ONE; - BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; - byte[] data = new byte[] { 0x01, 0x02 }; - - Executable action = () -> aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, - srcHeight, dstNetwork, data); - - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, and data cannot be null", - e.getMessage()); - } - - @Test - public void testRegisterPacket_duplicate() { - String srcNetwork = "0x2.icon"; - String dstNetwork = "sui"; - BigInteger srcSn = BigInteger.ONE; - BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; - byte[] data = new byte[] { 0x01, 0x02 }; - - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - - Executable action = () -> aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, - srcHeight, dstNetwork, data); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): Packet already exists", e.getMessage()); - } - - @Test - public void testAcknowledgePacket() throws Exception { + public void testSubmitPacket() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; @@ -228,19 +179,19 @@ public void testAcknowledgePacket() throws Exception { String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, sign); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + sign); String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); } @Test - public void testAcknowledgePacket_thresholdReached() throws Exception { + public void testSubmitPacket_thresholdReached() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; @@ -248,33 +199,32 @@ public void testAcknowledgePacket_thresholdReached() throws Exception { String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - byte[] dataHash = Context.hash("sha-256", data); - byte[] signOne = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, signOne); + byte[] signAdmin = admin.sign(dataHash); + aggregator.invoke(adminAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + signAdmin); - byte[] signTwo = relayerTwo.sign(dataHash); - aggregator.invoke(relayerTwoAc, "acknowledgePacket", srcNetwork, - contractAddress, srcSn, signTwo); + byte[] signOne = relayerOne.sign(dataHash); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + signOne); byte[][] sigs = new byte[2][]; - sigs[0] = signOne; - sigs[1] = signTwo; + sigs[0] = signAdmin; + sigs[1] = signOne; byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); - assertArrayEquals(signOne, decodedSigs[0]); - assertArrayEquals(signTwo, decodedSigs[1]); + assertArrayEquals(signAdmin, decodedSigs[0]); + assertArrayEquals(signOne, decodedSigs[1]); verify(aggregatorSpy).PacketAcknowledged(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, encodedSigs); } @Test - public void testAcknowledgePacket_unauthorized() throws Exception { + public void testSubmitPacket_unauthorized() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; @@ -282,14 +232,11 @@ public void testAcknowledgePacket_unauthorized() throws Exception { String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerFour.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerFourAc, "acknowledgePacket", srcNetwork, contractAddress, - srcSn, - sign); + Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", srcNetwork, contractAddress, srcSn, + srcHeight, dstNetwork, data, sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", @@ -297,7 +244,7 @@ public void testAcknowledgePacket_unauthorized() throws Exception { } @Test - public void testAcknowledgePacket_duplicate() throws Exception { + public void testSubmitPacket_duplicate() throws Exception { String srcNetwork = "0x2.icon"; String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; @@ -305,34 +252,17 @@ public void testAcknowledgePacket_duplicate() throws Exception { String contractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; - aggregator.invoke(adminAc, "registerPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); - byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, srcSn, sign); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, + data, sign); - Executable action = () -> aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, - srcSn, - sign); + Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, + srcHeight, dstNetwork, + data, sign); + ; UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Signature already exists", e.getMessage()); } - - @Test - public void testAcknowledgePacket_packetUnregistered() throws Exception { - String srcNetwork = "0x2.icon"; - BigInteger srcSn = BigInteger.ONE; - String contractAddress = "hxjuiod"; - byte[] data = new byte[] { 0x01, 0x02 }; - - byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerOne.sign(dataHash); - - Executable action = () -> aggregator.invoke(relayerOneAc, "acknowledgePacket", srcNetwork, contractAddress, - srcSn, - sign); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Packet not registered", e.getMessage()); - } } From 7291e2c6f12e64822403f1f4a869ec90e44a9b7e Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 4 Oct 2024 13:42:40 +0545 Subject: [PATCH 25/75] fix: add external method to check if packet is already submitted --- .../relay/aggregator/RelayAggregator.java | 11 +++++++ .../relay/aggregator/RelayAggregatorTest.java | 31 +++++++++++++++++++ 2 files changed, 42 insertions(+) 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 fd3a8ccd..3e2039fc 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -125,6 +125,17 @@ public void removeRelayers(Address[] relayersToBeRemoved) { } } + @External(readonly = true) + public boolean packetSubmitted( + Address relayer, + String srcNetwork, + String contractAddress, + BigInteger srcSn) { + String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + byte[] existingSign = signatures.at(pktID).get(relayer); + return existingSign != null; + } + @External public void submitPacket( String srcNetwork, 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 d043ea0d..ac8583a2 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -170,6 +170,37 @@ public void testRemoveRelayers_unauthorised() { assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); } + @Test + public void testPacketSubmitted_true() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String contractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash); + + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + sign); + + boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, + contractAddress, srcSn); + assertEquals(submitted, true); + } + + @Test + public void testPacketSubmitted_false() throws Exception { + String srcNetwork = "0x2.icon"; + BigInteger srcSn = BigInteger.ONE; + String contractAddress = "hxjuiod"; + + boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, + contractAddress, srcSn); + assertEquals(submitted, false); + } + @Test public void testSubmitPacket() throws Exception { String srcNetwork = "0x2.icon"; From c11c34a41aa0d3462aa5709ac65e926f8c1549e9 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 4 Oct 2024 15:49:29 +0545 Subject: [PATCH 26/75] fix: add dstContractAddress info in packet --- .../main/java/relay/aggregator/Packet.java | 46 +++++++++++----- .../relay/aggregator/RelayAggregator.java | 27 +++++---- .../relay/aggregator/RelayAggregatorTest.java | 55 ++++++++++++------- 3 files changed, 83 insertions(+), 45 deletions(-) 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 e481a235..0568de37 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java @@ -16,7 +16,7 @@ public class Packet { /** * The contract address on the source network (chain). */ - private final String contractAddress; + private final String srcContractAddress; /** * The sequence number of the packet in the source network (chain). @@ -33,6 +33,11 @@ public class Packet { */ private final String dstNetwork; + /** + * The contract address on the destination network (chain). + */ + private final String dstContractAddress; + /** * The payload data associated with this packet. */ @@ -47,29 +52,33 @@ public class Packet { * @param dstNetwork the ID of the destination network (chain). * @param data the payload data for this packet. * @throws IllegalArgumentException if {@code srcNetwork}, - * {@code contractAddress}, {@code srcSn}, + * {@code srcContractAddress}, {@code srcSn}, * {@code srcHeight}, - * {@code dstNetwork}, or {@code data} is + * {@code dstNetwork}, + * {@code dstContractAddress}, or {@code data} + * is * {@code null}. */ - public Packet(String srcNetwork, String contractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, + public Packet(String srcNetwork, String srcContractAddress, BigInteger srcSn, BigInteger srcHeight, + String dstNetwork, String dstContractAddress, byte[] data) { - Boolean isIllegalArg = srcNetwork == null || contractAddress == null || contractAddress == null || srcSn == null - || srcHeight == null || dstNetwork == null || data == null; + Boolean isIllegalArg = srcNetwork == null || srcContractAddress == null || srcSn == null + || 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.contractAddress = contractAddress; + this.srcContractAddress = srcContractAddress; this.srcSn = srcSn; this.srcHeight = srcHeight; this.dstNetwork = dstNetwork; + this.dstContractAddress = dstContractAddress; this.data = data; } public String getId() { - return createId(this.srcNetwork, this.contractAddress, this.srcSn); + return createId(this.srcNetwork, this.srcContractAddress, this.srcSn); } public static String createId(String srcNetwork, String contractAddress, BigInteger srcSn) { @@ -88,10 +97,10 @@ public String getSrcNetwork() { /** * Returns the contract address on the source network (chain). * - * @return the contract address. + * @return the source contract address. */ - public String getContractAddress() { - return contractAddress; + public String getSrcContractAddress() { + return srcContractAddress; } /** @@ -121,6 +130,15 @@ public String getDstNetwork() { return dstNetwork; } + /** + * Returns the contract address on the destination network (chain). + * + * @return the destination contract address. + */ + public String getDstContractAddress() { + return dstContractAddress; + } + /** * Returns a copy of the data associated with this packet. * @@ -131,12 +149,13 @@ public byte[] getData() { } public static void writeObject(ObjectWriter w, Packet p) { - w.beginList(6); + w.beginList(7); w.write(p.srcNetwork); - w.write(p.contractAddress); + w.write(p.srcContractAddress); w.write(p.srcSn); w.write(p.srcHeight); w.write(p.dstNetwork); + w.write(p.dstContractAddress); w.writeNullable(p.data); w.end(); } @@ -149,6 +168,7 @@ public static Packet readObject(ObjectReader r) { r.readBigInteger(), r.readBigInteger(), r.readString(), + r.readString(), r.readNullable(byte[].class)); r.end(); return p; 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 3e2039fc..18dca0df 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -129,9 +129,9 @@ public void removeRelayers(Address[] relayersToBeRemoved) { public boolean packetSubmitted( Address relayer, String srcNetwork, - String contractAddress, + String srcContractAddress, BigInteger srcSn) { - String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); byte[] existingSign = signatures.at(pktID).get(relayer); return existingSign != null; } @@ -139,26 +139,28 @@ public boolean packetSubmitted( @External public void submitPacket( String srcNetwork, - String contractAddress, + String srcContractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, + String dstContractAddress, byte[] data, byte[] signature) { relayersOnly(); - Packet pkt = new Packet(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); + Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data); String pktID = pkt.getId(); if (packets.get(pktID) == null) { packets.set(pktID, pkt); PacketRegistered( pkt.getSrcNetwork(), - pkt.getContractAddress(), + pkt.getSrcContractAddress(), pkt.getSrcSn(), pkt.getSrcHeight(), pkt.getDstNetwork(), + pkt.getDstContractAddress(), pkt.getData()); } @@ -168,22 +170,23 @@ public void submitPacket( setSignature(pktID, Context.getCaller(), signature); if (signatureThresholdReached(pktID)) { - byte[][] sigs = getSignatures(srcNetwork, contractAddress, srcSn); + byte[][] sigs = getSignatures(srcNetwork, srcContractAddress, srcSn); byte[] encodedSigs = serializeSignatures(sigs); PacketAcknowledged( pkt.getSrcNetwork(), - pkt.getContractAddress(), + pkt.getSrcContractAddress(), pkt.getSrcSn(), pkt.getSrcHeight(), pkt.getDstNetwork(), + pkt.getDstContractAddress(), pkt.getData(), encodedSigs); removePacket(pktID); } } - private byte[][] getSignatures(String srcNetwork, String contractAddress, BigInteger srcSn) { - String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); + private byte[][] getSignatures(String srcNetwork, String srcContractAddress, BigInteger srcSn) { + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); DictDB signDict = signatures.at(pktID); ArrayList signatureList = new ArrayList(); @@ -272,20 +275,22 @@ private void removePacket(String pktID) { @EventLog(indexed = 2) public void PacketRegistered( String srcNetwork, - String contractAddress, + String srcContractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, + String dstContractAddress, byte[] data) { } @EventLog(indexed = 2) public void PacketAcknowledged( String srcNetwork, - String contractAddress, + String srcContractAddress, BigInteger srcSn, BigInteger srcHeight, String dstNetwork, + String dstContractAddress, byte[] data, byte[] signatures) { } 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 ac8583a2..f06fae8d 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -176,17 +176,19 @@ public void testPacketSubmitted_true() throws Exception { String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, sign); boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, - contractAddress, srcSn); + srcContractAddress, srcSn); assertEquals(submitted, true); } @@ -194,10 +196,10 @@ public void testPacketSubmitted_true() throws Exception { public void testPacketSubmitted_false() throws Exception { String srcNetwork = "0x2.icon"; BigInteger srcSn = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, - contractAddress, srcSn); + srcContractAddress, srcSn); assertEquals(submitted, false); } @@ -207,17 +209,20 @@ public void testSubmitPacket() throws Exception { String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, sign); - String pktID = Packet.createId(srcNetwork, contractAddress, srcSn); - verify(aggregatorSpy).PacketRegistered(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data); + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); + verify(aggregatorSpy).PacketRegistered(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data); verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); } @@ -227,17 +232,21 @@ public void testSubmitPacket_thresholdReached() throws Exception { String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); byte[] signAdmin = admin.sign(dataHash); - aggregator.invoke(adminAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + aggregator.invoke(adminAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, signAdmin); byte[] signOne = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, + data, signOne); byte[][] sigs = new byte[2][]; @@ -250,7 +259,8 @@ public void testSubmitPacket_thresholdReached() throws Exception { assertArrayEquals(signAdmin, decodedSigs[0]); assertArrayEquals(signOne, decodedSigs[1]); - verify(aggregatorSpy).PacketAcknowledged(srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, data, + verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, encodedSigs); } @@ -260,14 +270,16 @@ public void testSubmitPacket_unauthorized() throws Exception { String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerFour.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", srcNetwork, contractAddress, srcSn, - srcHeight, dstNetwork, data, sign); + Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", srcNetwork, srcContractAddress, + srcSn, + srcHeight, dstNetwork, dstContractAddress, data, sign); UserRevertedException e = assertThrows(UserRevertedException.class, action); assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", @@ -280,17 +292,18 @@ public void testSubmitPacket_duplicate() throws Exception { String dstNetwork = "sui"; BigInteger srcSn = BigInteger.ONE; BigInteger srcHeight = BigInteger.ONE; - String contractAddress = "hxjuiod"; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, - data, sign); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, sign); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, contractAddress, srcSn, - srcHeight, dstNetwork, + Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, dstContractAddress, data, sign); ; UserRevertedException e = assertThrows(UserRevertedException.class, action); From 7d7031aa05182e145be3f00ee9531d47baf44750 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Mon, 7 Oct 2024 12:19:35 +0545 Subject: [PATCH 27/75] fix: for a single relayer cluster do not emit PacketRegistered event --- .../relay/aggregator/RelayAggregator.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 18dca0df..12203c70 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -67,6 +67,7 @@ public Address getAdmin() { @External public void setSignatureThreshold(int threshold) { adminOnly(); + Context.require(threshold >= 1, "invalid threshold value: should be >= 1"); signatureThreshold.set(threshold); } @@ -154,14 +155,17 @@ public void submitPacket( if (packets.get(pktID) == null) { packets.set(pktID, pkt); - PacketRegistered( - pkt.getSrcNetwork(), - pkt.getSrcContractAddress(), - pkt.getSrcSn(), - pkt.getSrcHeight(), - pkt.getDstNetwork(), - pkt.getDstContractAddress(), - pkt.getData()); + if (signatureThreshold.get() > 1) { + PacketRegistered( + pkt.getSrcNetwork(), + pkt.getSrcContractAddress(), + pkt.getSrcSn(), + pkt.getSrcHeight(), + pkt.getDstNetwork(), + pkt.getDstContractAddress(), + pkt.getData()); + } + } byte[] existingSign = signatures.at(pktID).get(Context.getCaller()); From 721768e3c115172b81d8f505826d8fd619202cdb Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 7 Oct 2024 15:27:46 +0545 Subject: [PATCH 28/75] chore: updated check and reverted as per PR comments --- .../contracts/adapters/ClusterConnection.sol | 6 +-- .../evm/test/adapters/ClusterConnection.t.sol | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index f0805e9c..0f162046 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -143,10 +143,8 @@ contract ClusterConnection is Initializable, IConnection { signerCount++; } } - - if (signerCount >= reqValidatorCnt) { - recvMessage(srcNetwork,_connSn,_msg); - } + require(signerCount >= reqValidatorCnt,"Not enough valid signatures passed"); + recvMessage(srcNetwork,_connSn,_msg); } function isValidatorProcessed(address[] memory processedSigners, address signer) public pure returns (bool) { diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 46e35e83..3955a5c9 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -415,4 +415,42 @@ contract ClusterConnectionTest is Test { assertEq(3, adapterTarget.getRequiredValidatorCount()); vm.stopPrank(); } + + function testRecvMessageWithMultiSignatureNotEnoughSign() public { + bytes memory data = bytes("test"); + string memory iconDapp = NetworkAddress.networkAddress( + nidSource, + "0xa" + ); + Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( + iconDapp, + ParseAddress.toString(address(dappSource)), + 1, + Types.CALL_MESSAGE_TYPE, + data, + new string[](0) + ); + Types.CSMessage memory message = Types.CSMessage( + Types.CS_REQUEST, + request.encodeCSMessageRequestV2() + ); + uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + uint256 pk2 = hexStringToUint256("47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"); + bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); + vm.startPrank(destination_relayer); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addValidator(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); + adapterTarget.setRequiredValidatorCount(4); + vm.expectRevert("Not enough signatures passed"); + bytes[] memory signatures = new bytes[](2) ; + signatures[0] = signMessage(pk,hash); + signatures[1] = signMessage(pk2,hash); + adapterTarget.recvMessageWithSignatures( + nidSource, + 1, + RLPEncodeStruct.encodeCSMessage(message), + signatures + ); + vm.stopPrank(); + } } From 70ad7558a5732a98bcc65eff70c2cd05eb4237c2 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 7 Oct 2024 15:34:31 +0545 Subject: [PATCH 29/75] chore: updated check and reverted if not enough valid sigs --- .../xcall/adapter/cluster/ClusterConnection.java | 5 ++--- .../adapter/cluster/ClusterConnectionTest.java | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 0044e441..2cbc74c5 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -229,9 +229,8 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt uniqueValidators.add(validator); } } - if (uniqueValidators.size() >= reqValidatorCnt.get().intValue()) { - recvMessage(srcNetwork, _connSn, msg); - } + Context.require(uniqueValidators.size() >= reqValidatorCnt.get().intValue(), "Not enough valid signatures"); + recvMessage(srcNetwork, _connSn, msg); } private boolean validatorExists(Address _validator) { diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 61438ad1..52f82be0 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -251,6 +251,22 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ verifyNoInteractions(callservice.mock); } + @Test + public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Exception{ + byte[] data = "test".getBytes(); + byte[] messageHash = keccak256(data); + KeyWallet wallet = KeyWallet.create(); + byte[][] byteArray = new byte[2][]; + byteArray[0] = wallet.sign(messageHash); + byteArray[1] = wallet.sign(messageHash); + connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); + assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); + verifyNoInteractions(callservice.mock); + } + public static byte[] keccak256(byte[] input) { Keccak.Digest256 keccak256 = new Keccak.Digest256(); From 2a6d8e473344f53b2ac82f9926d684edd0353b36 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 7 Oct 2024 15:35:53 +0545 Subject: [PATCH 30/75] chore: updated TC --- contracts/evm/test/adapters/ClusterConnection.t.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 3955a5c9..7720b3d7 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -435,16 +435,15 @@ contract ClusterConnectionTest is Test { request.encodeCSMessageRequestV2() ); uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); - uint256 pk2 = hexStringToUint256("47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"); bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); vm.startPrank(destination_relayer); adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); adapterTarget.addValidator(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); - adapterTarget.setRequiredValidatorCount(4); - vm.expectRevert("Not enough signatures passed"); + adapterTarget.setRequiredValidatorCount(2); + vm.expectRevert("Not enough valid signatures passed"); bytes[] memory signatures = new bytes[](2) ; signatures[0] = signMessage(pk,hash); - signatures[1] = signMessage(pk2,hash); + signatures[1] = signMessage(pk,hash); adapterTarget.recvMessageWithSignatures( nidSource, 1, From 08508f562d07c76316a986334b7403e9a49851c0 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Mon, 7 Oct 2024 17:46:27 +0545 Subject: [PATCH 31/75] fix: set default threshold to 1 && rf: relayers add and remove --- .../relay/aggregator/RelayAggregator.java | 47 +++++++++++-------- .../relay/aggregator/RelayAggregatorTest.java | 24 +++++++++- 2 files changed, 50 insertions(+), 21 deletions(-) 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 12203c70..f4f41542 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -29,10 +29,9 @@ import score.annotation.External; import score.ObjectReader; import scorex.util.ArrayList; -import scorex.util.HashMap; public class RelayAggregator { - private final Integer DEFAULT_SIGNATURE_THRESHOLD = 2; + private final Integer DEFAULT_SIGNATURE_THRESHOLD = 1; private final VarDB signatureThreshold = Context.newVarDB("signatureThreshold", Integer.class); @@ -50,12 +49,20 @@ public RelayAggregator(Address _admin) { if (admin.get() == null) { admin.set(_admin); signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD); + addRelayer(_admin); } } @External public void setAdmin(Address _admin) { adminOnly(); + + // add new admin as relayer + addRelayer(_admin); + + // remove old admin from relayer list + removeRelayer(admin.get()); + admin.set(_admin); } @@ -94,8 +101,7 @@ public void addRelayers(Address[] newRelayers) { for (Address newRelayer : newRelayers) { Boolean exits = relayersLookup.get(newRelayer); if (exits == null) { - relayers.add(newRelayer); - relayersLookup.set(newRelayer, true); + addRelayer(newRelayer); } } } @@ -107,22 +113,9 @@ public void removeRelayers(Address[] relayersToBeRemoved) { Context.require(relayersToBeRemoved != null && relayersToBeRemoved.length != 0, "relayers to be removed cannot be empty"); - HashMap existingRelayers = new HashMap(); - for (int i = 0; i < relayers.size(); i++) { - Address relayer = relayers.get(i); - existingRelayers.put(relayer, i); - } - for (Address relayerToBeRemoved : relayersToBeRemoved) { - if (existingRelayers.containsKey(relayerToBeRemoved)) { - relayersLookup.set(relayerToBeRemoved, null); - Address top = relayers.pop(); - if (!top.equals(relayerToBeRemoved)) { - Integer pos = existingRelayers.get(relayerToBeRemoved); - relayers.set(pos, top); - } - existingRelayers.remove(relayerToBeRemoved); - } + Context.require(relayerToBeRemoved != admin.get(), "admin cannot be removed from relayers list"); + removeRelayer(relayerToBeRemoved); } } @@ -254,6 +247,22 @@ private void relayersOnly() { Context.require(isRelayer != null && isRelayer, "Unauthorized: caller is not a registered relayer"); } + private void addRelayer(Address newRelayer) { + relayers.add(newRelayer); + relayersLookup.set(newRelayer, true); + } + + private void removeRelayer(Address oldRelayer) { + relayersLookup.set(oldRelayer, null); + Address top = relayers.pop(); + for (int i = 0; i < relayers.size(); i++) { + if (oldRelayer.equals(relayers.get(i))) { + relayers.set(i, top); + break; + } + } + } + private Boolean signatureThresholdReached(String pktID) { int noOfSignatures = 0; for (int i = 0; i < relayers.size(); i++) { 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 f06fae8d..d44e84a2 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -1,6 +1,7 @@ package relay.aggregator; import java.math.BigInteger; +import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,6 +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; @@ -75,11 +77,21 @@ 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 result = (Address) aggregator.call("getAdmin"); - assertEquals(newAdminAc.getAddress(), result); + 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 @@ -180,6 +192,8 @@ public void testPacketSubmitted_true() throws Exception { 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); @@ -213,6 +227,8 @@ public void testSubmitPacket() throws Exception { 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); @@ -236,6 +252,8 @@ public void testSubmitPacket_thresholdReached() throws Exception { String dstContractAddress = "hxjuiod"; byte[] data = new byte[] { 0x01, 0x02 }; + aggregator.invoke(adminAc, "setSignatureThreshold", 2); + byte[] dataHash = Context.hash("sha-256", data); byte[] signAdmin = admin.sign(dataHash); @@ -296,6 +314,8 @@ public void testSubmitPacket_duplicate() throws Exception { 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); From ef35f053da03fa280b6b28fbd5b65e8b890c4c03 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Tue, 8 Oct 2024 10:55:04 +0545 Subject: [PATCH 32/75] chore: added checks during removal of validators --- .../contracts/adapters/ClusterConnection.sol | 4 ++- .../evm/test/adapters/ClusterConnection.t.sol | 25 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index 0f162046..7118c3d1 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -47,7 +47,8 @@ contract ClusterConnection is Initializable, IConnection { } function removeValidator(address _validator) external onlyAdmin { - require(_validator!=this.admin(), "cannot remove admin"); + require(_validator!=this.admin(), "Cannot remove admin"); + require(isValidator[_validator],"Validator doesn't exist"); for (uint i = 0; i < validators.length; i++) { if (validators[i] == _validator) { validators[i] = validators[validators.length - 1]; @@ -57,6 +58,7 @@ contract ClusterConnection is Initializable, IConnection { } emit ValidatorRemoved(_validator); } + require(validators.length>=reqValidatorCnt,"Validator size less than required count after removal"); } /** diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 7720b3d7..e45262e5 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -399,16 +399,37 @@ contract ClusterConnectionTest is Test { function testRemoveValidator() public { vm.startPrank(destination_relayer); + adapterTarget.setRequiredValidatorCount(2); adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); assertEq(3, adapterTarget.listValidators().length); - adapterTarget.removeValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); - assertEq(3, adapterTarget.listValidators().length); adapterTarget.removeValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); assertEq(2, adapterTarget.listValidators().length); vm.stopPrank(); } + + function testRemoveNonExistentValidator() public { + vm.startPrank(destination_relayer); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(3, adapterTarget.listValidators().length); + vm.expectRevert("Validator doesn't exist"); + adapterTarget.removeValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); + vm.stopPrank(); + } + + function testRemoveValidatorSize() public { + vm.startPrank(destination_relayer); + adapterTarget.setRequiredValidatorCount(3); + adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); + adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + assertEq(3, adapterTarget.listValidators().length); + vm.expectRevert("Validator size less than required count after removal"); + adapterTarget.removeValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + vm.stopPrank(); + } + function testRequiredCount() public { vm.startPrank(destination_relayer); adapterTarget.setRequiredValidatorCount(3); From 8943444161b042135785f4b9890f642f236dd4c6 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Tue, 8 Oct 2024 11:24:45 +0545 Subject: [PATCH 33/75] fix: revert early --- contracts/evm/contracts/adapters/ClusterConnection.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index 7118c3d1..f9aee6e8 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -49,6 +49,7 @@ contract ClusterConnection is Initializable, IConnection { function removeValidator(address _validator) external onlyAdmin { require(_validator!=this.admin(), "Cannot remove admin"); require(isValidator[_validator],"Validator doesn't exist"); + require(validators.length-1>=reqValidatorCnt,"Validator size less than required count after removal"); for (uint i = 0; i < validators.length; i++) { if (validators[i] == _validator) { validators[i] = validators[validators.length - 1]; @@ -58,7 +59,7 @@ contract ClusterConnection is Initializable, IConnection { } emit ValidatorRemoved(_validator); } - require(validators.length>=reqValidatorCnt,"Validator size less than required count after removal"); + } /** From b5efc7720f447fc3326bbe3884d068adc1bc8591 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Tue, 8 Oct 2024 11:55:20 +0545 Subject: [PATCH 34/75] fix: revert early and added lookup for validators --- .../adapter/cluster/ClusterConnection.java | 39 ++++++++----------- .../cluster/ClusterConnectionTest.java | 15 +++---- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 2cbc74c5..bb440c38 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -44,6 +44,7 @@ public class ClusterConnection { protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); protected final BranchDB> receipts = Context.newBranchDB("receipts", Boolean.class); + private final DictDB validatorsLookup = Context.newDictDB("validatorsLookup", Boolean.class); public ClusterConnection(Address _relayer, Address _xCall) { if (xCall.get() == null) { @@ -51,6 +52,7 @@ public ClusterConnection(Address _relayer, Address _xCall) { adminAddress.set(_relayer); connSn.set(BigInteger.ZERO); validators.add(_relayer); + validatorsLookup.set(_relayer,true); ValidatorAdded(_relayer); } } @@ -72,28 +74,30 @@ public Address[] listValidators() { @External public void addValidator(Address _validator) { OnlyAdmin(); - if (!validatorExists(_validator)){ - validators.add(_validator); - ValidatorAdded(_validator); - } + Context.require(validatorsLookup.get(_validator)==null,"Validator already exists"); + validators.add(_validator); + validatorsLookup.set(_validator,true); + ValidatorAdded(_validator); } @External public void removeValidator(Address _validator) { OnlyAdmin(); Context.require(_validator != adminAddress.get(),"cannot remove admin"); - if (validatorExists(_validator)){ - Address top = this.validators.pop(); - if (!top.equals(_validator)) { - for (int i = 0; i < this.validators.size(); i++) { - if (_validator.equals(this.validators.get(i))) { - this.validators.set(i, top); - break; - } + Context.require(validatorsLookup.get(_validator)!=null,"Validator doesn't exists"); + Context.require((this.validators.size() - 1) >= reqValidatorCnt.get().intValue(),"Validator size less than required count after removal"); + Address top = this.validators.pop(); + if (!top.equals(_validator)) { + for (int i = 0; i < this.validators.size(); i++) { + if (_validator.equals(this.validators.get(i))) { + this.validators.set(i, top); + break; } } + validatorsLookup.set(_validator,null); ValidatorRemoved(_validator); } + } @EventLog(indexed = 2) @@ -224,7 +228,7 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { Address validator = getValidator(msg, signature); - Context.require(validatorExists(validator), "Invalid signature provided"); + Context.require(validatorsLookup.get(validator)!=null, "Invalid signature provided"); if (!uniqueValidators.contains(validator)) { uniqueValidators.add(validator); } @@ -233,15 +237,6 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt recvMessage(srcNetwork, _connSn, msg); } - private boolean validatorExists(Address _validator) { - for (int i = 0; i < validators.size(); i++) { - if (validators.get(i).equals(_validator)) { - return true; - } - } - return false; - } - /** * Receives a message from a source network. * diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 52f82be0..fd021c14 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -284,18 +284,19 @@ public void testAddSigners() throws Exception{ @Test public void testAddNRemoveSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - KeyWallet wallet2 = KeyWallet.create(); KeyWallet wallet3 = KeyWallet.create(); connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); + connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 3); + assertEquals(signers.length, 2); - connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString())); - signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 3); + UserRevertedException e = assertThrows(UserRevertedException.class, + ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString()))); + assertEquals("Reverted(0): Validator doesn't exists", e.getMessage()); - connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet2.getAddress().toString())); + UserRevertedException ex = assertThrows(UserRevertedException.class, + ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet.getAddress().toString()))); + assertEquals("Reverted(0): Validator size less than required count after removal", ex.getMessage()); signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 21b929cc1460c509a3551c81868111b567cd8859 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Wed, 9 Oct 2024 10:36:14 +0545 Subject: [PATCH 35/75] fix: checkpoint --- Cargo.lock | 143 +++++++- contracts/soroban/Cargo.lock | 274 ++++++++++++-- .../contracts/cluster-connection/Cargo.toml | 17 + .../cluster-connection/src/contract.rs | 192 ++++++++++ .../cluster-connection/src/errors.rs | 18 + .../contracts/cluster-connection/src/event.rs | 19 + .../cluster-connection/src/helpers.rs | 114 ++++++ .../src/interfaces/interface_xcall.rs | 15 + .../cluster-connection/src/interfaces/mod.rs | 1 + .../contracts/cluster-connection/src/lib.rs | 10 + .../cluster-connection/src/storage.rs | 190 ++++++++++ .../contracts/cluster-connection/src/test.rs | 345 ++++++++++++++++++ .../contracts/cluster-connection/src/types.rs | 38 ++ 13 files changed, 1320 insertions(+), 56 deletions(-) create mode 100644 contracts/soroban/contracts/cluster-connection/Cargo.toml create mode 100644 contracts/soroban/contracts/cluster-connection/src/contract.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/errors.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/event.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/helpers.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/interfaces/interface_xcall.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/interfaces/mod.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/lib.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/storage.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/test.rs create mode 100644 contracts/soroban/contracts/cluster-connection/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 36dc3585..f00ebfe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -160,7 +160,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -285,6 +285,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "clear_on_drop" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38508a63f4979f0048febc9966fadbd48e5dab31fd0ec6a3f151bbf4a74f7423" +dependencies = [ + "cc", +] + +[[package]] +name = "cluster-connection" +version = "0.0.0" +dependencies = [ + "curve25519-dalek 1.2.6", + "ed25519-dalek 1.0.1", + "soroban-sdk", +] + [[package]] name = "common" version = "0.1.0" @@ -455,7 +473,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -467,7 +485,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -479,7 +497,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] @@ -493,6 +511,19 @@ dependencies = [ "syn 2.0.42", ] +[[package]] +name = "curve25519-dalek" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d59fed08e452f286b251f88b2fc64a01f50a7b263aa09557ad7285d9e7fa" +dependencies = [ + "byteorder", + "clear_on_drop", + "digest 0.8.1", + "rand_core 0.3.1", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1024,13 +1055,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -1132,6 +1172,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand 0.7.3", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "2.1.1" @@ -1179,7 +1232,7 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array", + "generic-array 0.14.7", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", @@ -1198,7 +1251,7 @@ dependencies = [ "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.0", - "generic-array", + "generic-array 0.14.7", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", @@ -1387,6 +1440,15 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2181,6 +2243,17 @@ dependencies = [ "proc-macro2 1.0.70", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -2188,10 +2261,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2202,6 +2285,21 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.5.1" @@ -2217,6 +2315,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2455,7 +2562,7 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.9.0", "subtle", "zeroize", @@ -2469,7 +2576,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.9", - "generic-array", + "generic-array 0.14.7", "pkcs8 0.10.2", "subtle", "zeroize", @@ -2733,9 +2840,9 @@ dependencies = [ "backtrace", "curve25519-dalek 4.1.3", "ecdsa 0.16.9", - "ed25519-dalek", + "ed25519-dalek 2.1.1", "elliptic-curve 0.13.8", - "generic-array", + "generic-array 0.14.7", "getrandom", "hex-literal 0.4.1", "hmac", @@ -2744,8 +2851,8 @@ dependencies = [ "num-integer", "num-traits", "p256", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "sec1 0.7.3", "sha2 0.10.8", "sha3", @@ -2802,8 +2909,8 @@ dependencies = [ "arbitrary", "bytes-lit", "ctor", - "ed25519-dalek", - "rand", + "ed25519-dalek 2.1.1", + "rand 0.8.5", "serde", "serde_json", "soroban-env-guest", @@ -3101,7 +3208,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test 0.16.4", "hex", - "hex-literal 0.3.4", + "hex-literal 0.4.1", "ibc-proto", "prost 0.11.9", "serde", diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index e571a9ff..b02fd6ef 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -92,13 +92,22 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] [[package]] @@ -107,6 +116,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes-lit" version = "0.0.5" @@ -151,6 +166,24 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clear_on_drop" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38508a63f4979f0048febc9966fadbd48e5dab31fd0ec6a3f151bbf4a74f7423" +dependencies = [ + "cc", +] + +[[package]] +name = "cluster-connection" +version = "0.0.0" +dependencies = [ + "curve25519-dalek 1.2.6", + "ed25519-dalek 1.0.1", + "soroban-sdk", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -189,8 +222,8 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", - "rand_core", + "generic-array 0.14.7", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -201,7 +234,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", "typenum", ] @@ -215,6 +248,32 @@ dependencies = [ "syn", ] +[[package]] +name = "curve25519-dalek" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d59fed08e452f286b251f88b2fc64a01f50a7b263aa09557ad7285d9e7fa" +dependencies = [ + "byteorder", + "clear_on_drop", + "digest 0.8.1", + "rand_core 0.3.1", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.2" @@ -224,7 +283,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", "platforms", "rustc_version", @@ -309,13 +368,31 @@ dependencies = [ "syn", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -334,13 +411,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", - "signature", + "signature 2.2.0", "spki", ] +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + [[package]] name = "ed25519" version = "2.2.3" @@ -348,7 +434,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand 0.7.3", + "sha2 0.9.9", + "zeroize", ] [[package]] @@ -357,11 +456,11 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ - "curve25519-dalek", - "ed25519", - "rand_core", + "curve25519-dalek 4.1.2", + "ed25519 2.2.3", + "rand_core 0.6.4", "serde", - "sha2", + "sha2 0.10.8", "zeroize", ] @@ -379,12 +478,12 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.7", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -414,7 +513,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -430,6 +529,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -467,7 +575,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -504,7 +612,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -598,8 +706,8 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", - "signature", + "sha2 0.10.8", + "signature 2.2.0", ] [[package]] @@ -716,6 +824,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "paste" version = "1.0.14" @@ -778,6 +892,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -785,8 +910,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -796,9 +931,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", ] +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -808,6 +964,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -847,7 +1012,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array", + "generic-array 0.14.7", "pkcs8", "subtle", "zeroize", @@ -920,6 +1085,19 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" @@ -928,7 +1106,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", ] [[package]] @@ -937,18 +1115,24 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] @@ -1004,8 +1188,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" dependencies = [ "backtrace", - "curve25519-dalek", - "ed25519-dalek", + "curve25519-dalek 4.1.2", + "ed25519-dalek 2.0.0", "getrandom", "hex-literal", "hmac", @@ -1013,9 +1197,9 @@ dependencies = [ "num-derive", "num-integer", "num-traits", - "rand", - "rand_chacha", - "sha2", + "rand 0.8.5", + "rand_chacha 0.3.1", + "sha2 0.10.8", "sha3", "soroban-builtin-sdk-macros", "soroban-env-common", @@ -1069,8 +1253,8 @@ dependencies = [ "arbitrary", "bytes-lit", "ctor", - "ed25519-dalek", - "rand", + "ed25519-dalek 2.0.0", + "rand 0.8.5", "serde", "serde_json", "soroban-env-guest", @@ -1092,7 +1276,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "sha2", + "sha2 0.10.8", "soroban-env-common", "soroban-spec", "soroban-spec-rust", @@ -1121,7 +1305,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "sha2", + "sha2 0.10.8", "soroban-spec", "stellar-xdr", "syn", @@ -1472,3 +1656,17 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/contracts/soroban/contracts/cluster-connection/Cargo.toml b/contracts/soroban/contracts/cluster-connection/Cargo.toml new file mode 100644 index 00000000..3fded6ee --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/Cargo.toml @@ -0,0 +1,17 @@ +[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"] } +ed25519-dalek = { version = "1.0.1", default-features = false, features = ["alloc"] } +curve25519-dalek = { version = "1", default-features = false, features = ["alloc"] } + +[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 00000000..92f72ece --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs @@ -0,0 +1,192 @@ +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_admin(&env, msg.relayer); + storage::store_xcall(&env, msg.xcall_address); + storage::store_upgrade_authority(&env, msg.upgrade_authority); + + 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 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( + env: Env, + src_network: String, + conn_sn: u128, + msg: Bytes, + ) -> Result<(), ContractError> { + helpers::ensure_admin(&env)?; + + 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 revert_message(env: &Env, sn: u128) -> Result<(), ContractError> { + helpers::ensure_admin(&env)?; + helpers::call_xcall_handle_error(&env, sn)?; + + Ok(()) + } + + pub fn recv_message_with_signatures( + env: Env, + src_network: String, + conn_sn: u128, + msg: Bytes, + signatures: Vec>, + ) -> Result<(), ContractError> { + helpers::ensure_admin(&env)?; + + if !helpers::verify_signatures(&env, signatures, msg.clone()){ + 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_admin(&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_admin(&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 add_validator(env: Env, address: Address) -> Result<(), ContractError> { + helpers::ensure_admin(&env)?; + let validators = storage::get_validators(&env).unwrap(); + if validators.contains(&address) { + return Err(ContractError::ValidatorAlreadyAdded); + } + storage::add_validator(&env, address); + Ok(()) + } + + pub fn remove_validator(env: Env, address: Address) -> Result<(), ContractError> { + helpers::ensure_admin(&env)?; + let validators: Vec
= storage::get_validators(&env).unwrap(); + let threshold = storage::get_validators_threshold(&env).unwrap(); + let admin = storage::admin(&env).unwrap(); + + let index = validators.iter().position(|v| v == address); + if index.is_none() { + return Err(ContractError::ValidatorNotFound); + } + if address == admin { + return Err(ContractError::CannotRemoveAdmin); + } + if (validators.len() as u32) <= threshold { + return Err(ContractError::ThresholdExceeded); + } + storage::remove_validator(&env, address); + Ok(()) + } + + pub fn get_validators(env: Env) -> Result, ContractError> { + let validators = storage::get_validators(&env).unwrap(); + Ok(validators) + } + + 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 00000000..b4d50517 --- /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 00000000..6db6d215 --- /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 00000000..a639a1f7 --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -0,0 +1,114 @@ +use core::panic; + +use soroban_sdk::{token, Address, Bytes, BytesN, Env, FromVal, Map, String, Vec}; +use ed25519_dalek::{PublicKey, Signature, Verifier}; +use crate::{errors::ContractError, interfaces::interface_xcall::XcallClient, storage}; + +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>, + 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().sha256(&message); + let mut unique_validators = Map::new(e); + let mut count = 0; + + + for signature in signatures.iter() { + if let Ok(sig) = convert_signature_to_dalek(&signature) { + for validator in validators.iter() { + if let Ok(public_key) = convert_address_to_public_key(e, &validator) { + if public_key.verify(&message_hash.to_array(), &sig).is_ok() { + if !unique_validators.contains_key(validator.clone()) { + unique_validators.set(validator, count); + count += 1; + } + break; + } + } + } + } + } + (unique_validators.len() as u32) >= threshold + +} + + +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(()) +} + +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(()) +} + +fn convert_address_to_public_key(e: &Env, address: &Address) -> Result { + let bytes: BytesN<32> = BytesN::from_val(e, &address.to_val()); + PublicKey::from_bytes(&bytes.to_array()) +} + +// Helper function to convert a BytesN<64> to ed25519_dalek::Signature +fn convert_signature_to_dalek(signature: &BytesN<64>) -> Result { + Signature::from_bytes(&signature.to_array()) +} \ No newline at end of file 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 00000000..c49d6c9c --- /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 00000000..61f8fdfe --- /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 00000000..1fd06073 --- /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 00000000..256a44bc --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/src/storage.rs @@ -0,0 +1,190 @@ +use soroban_sdk::{vec, Address, 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 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_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 remove_validator(e: &Env, address: Address) { + let mut validators: Vec
= get_validators(e).unwrap(); + if let Some(index) = validators.iter().position(|v| v == address) { + validators.remove(index.try_into().unwrap()); + e.storage().instance().set(&StorageKey::Validators, &validators); + } +} + +pub fn add_validator(e: &Env, address: Address) { + let mut validators = get_validators(e).unwrap(); + validators.push_back(address); + store_validators(e, 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 00000000..0e41564c --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/src/test.rs @@ -0,0 +1,345 @@ +#![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, Env, IntoVal, String, Symbol, +}; + +pub struct TestContext { + env: Env, + xcall: Address, + contract: 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); + Self { + xcall: env.register_contract_wasm(None, xcall::WASM), + contract: env.register_contract(None, ClusterConnection), + relayer: Address::generate(&env), + native_token: env.register_stellar_asset_contract(token_admin.clone()), + nid: String::from_str(&env, "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 { + 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 { + 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.relayer) +} + +#[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.relayer.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) +} 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 00000000..cffcfc46 --- /dev/null +++ b/contracts/soroban/contracts/cluster-connection/src/types.rs @@ -0,0 +1,38 @@ +use soroban_sdk::{contracttype, Address, String}; + +#[contracttype] +#[derive(Clone)] +pub enum StorageKey { + Xcall, + Admin, + UpgradeAuthority, + Xlm, + ConnSn, + NetworkFee(String), + Receipts(String, u128), + Validators, + ValidatorThreshold +} + +#[contracttype] +pub struct InitializeMsg { + pub relayer: 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, + } + } +} From e76f07b60a739b8d1e19d052865acc86f38601c2 Mon Sep 17 00:00:00 2001 From: AntonAndell Date: Sun, 13 Oct 2024 10:02:30 +0200 Subject: [PATCH 36/75] fix: Add auth on handle_error --- contracts/soroban/contracts/xcall/src/handle_message.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/soroban/contracts/xcall/src/handle_message.rs b/contracts/soroban/contracts/xcall/src/handle_message.rs index 3506b45e..56831a99 100644 --- a/contracts/soroban/contracts/xcall/src/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/handle_message.rs @@ -160,6 +160,7 @@ pub fn handle_reply( } pub fn handle_error(env: &Env, sender: Address, sequence_no: u128) -> Result<(), ContractError> { + sender.require_auth(); let cs_message_result = CSMessageResult::new( sequence_no, CSResponseType::CSResponseFailure, From f061016382aff53493aac745efc5d2ffd672f9d8 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Wed, 16 Oct 2024 11:28:33 +0545 Subject: [PATCH 37/75] fix: stellar ecdsa signing key --- .../contracts/cluster-connection/Cargo.toml | 2 - .../cluster-connection/src/contract.rs | 15 ++- .../cluster-connection/src/helpers.rs | 66 ++++++---- .../contracts/cluster-connection/src/test.rs | 116 +++++++++++++++++- .../contracts/mock-dapp-multi/src/test.rs | 6 +- .../mock-dapp-multi/src/test/setup.rs | 2 + .../soroban/contracts/xcall/src/test/setup.rs | 1 + 7 files changed, 176 insertions(+), 32 deletions(-) diff --git a/contracts/soroban/contracts/cluster-connection/Cargo.toml b/contracts/soroban/contracts/cluster-connection/Cargo.toml index 3fded6ee..be7f7e11 100644 --- a/contracts/soroban/contracts/cluster-connection/Cargo.toml +++ b/contracts/soroban/contracts/cluster-connection/Cargo.toml @@ -10,8 +10,6 @@ doctest = false [dependencies] soroban-sdk = { workspace = true, features = ["alloc"] } -ed25519-dalek = { version = "1.0.1", default-features = false, features = ["alloc"] } -curve25519-dalek = { version = "1", default-features = false, features = ["alloc"] } [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 index 92f72ece..c9502f03 100644 --- a/contracts/soroban/contracts/cluster-connection/src/contract.rs +++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs @@ -15,6 +15,8 @@ impl ClusterConnection { storage::store_admin(&env, msg.relayer); 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(()) } @@ -95,7 +97,7 @@ impl ClusterConnection { src_network: String, conn_sn: u128, msg: Bytes, - signatures: Vec>, + signatures: Vec>, ) -> Result<(), ContractError> { helpers::ensure_admin(&env)?; @@ -145,6 +147,17 @@ impl ClusterConnection { 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)?; + storage::store_validator_threshold(&env, threshold); + Ok(()) + } + pub fn remove_validator(env: Env, address: Address) -> Result<(), ContractError> { helpers::ensure_admin(&env)?; let validators: Vec
= storage::get_validators(&env).unwrap(); diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs index a639a1f7..6f88586e 100644 --- a/contracts/soroban/contracts/cluster-connection/src/helpers.rs +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -1,7 +1,6 @@ use core::panic; use soroban_sdk::{token, Address, Bytes, BytesN, Env, FromVal, Map, String, Vec}; -use ed25519_dalek::{PublicKey, Signature, Verifier}; use crate::{errors::ContractError, interfaces::interface_xcall::XcallClient, storage}; pub fn ensure_admin(e: &Env) -> Result { @@ -51,9 +50,29 @@ pub fn transfer_token( Ok(()) } +pub fn compress_public_keys(e: &Env, uncompressed_public_key: BytesN<65>) -> Bytes { + let uncompressed_pub_key_array = uncompressed_public_key.to_array(); + if uncompressed_pub_key_array[0] != 0x04 { + //return empty bytessize(33); + return Bytes::from_array(e, &[0u8; 33]); + } + + let x = &uncompressed_pub_key_array[1..33]; + let y = &uncompressed_pub_key_array[33..65]; + + let prefix = if y[31] % 2 == 0 { 0x02 } else { 0x03 }; + + let mut compressed_pub_key_array = [0u8; 33]; + compressed_pub_key_array[0] = prefix; + compressed_pub_key_array[1..].copy_from_slice(x); + let compressed_pub_key = Bytes::from_array(e, &compressed_pub_key_array); + + compressed_pub_key +} + pub fn verify_signatures( e: &Env, - signatures: Vec>, + signatures: Vec>, message: Bytes, ) -> bool { let validators = storage::get_validators(e).unwrap(); @@ -67,19 +86,26 @@ pub fn verify_signatures( let mut count = 0; - for signature in signatures.iter() { - if let Ok(sig) = convert_signature_to_dalek(&signature) { - for validator in validators.iter() { - if let Ok(public_key) = convert_address_to_public_key(e, &validator) { - if public_key.verify(&message_hash.to_array(), &sig).is_ok() { - if !unique_validators.contains_key(validator.clone()) { - unique_validators.set(validator, count); - count += 1; - } - break; - } - } - } + 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_id = r_s_v[64] as u32; // recovery ID + + let signature = BytesN::<64>::from_array(e, &signature_array); + + let uncompressed_public_key = e.crypto().secp256k1_recover(&message_hash, &signature, recovery_id); + + let compressed_pub_key = compress_public_keys(e, uncompressed_public_key); + + let stellar_address = Address::from_string_bytes(&compressed_pub_key); + + if validators.contains(&stellar_address) { + if !unique_validators.contains_key(stellar_address.clone()) { + unique_validators.set(stellar_address, count); + count += 1; + } } } (unique_validators.len() as u32) >= threshold @@ -102,13 +128,3 @@ pub fn call_xcall_handle_error(e: &Env, sn: u128) -> Result<(), ContractError> { Ok(()) } - -fn convert_address_to_public_key(e: &Env, address: &Address) -> Result { - let bytes: BytesN<32> = BytesN::from_val(e, &address.to_val()); - PublicKey::from_bytes(&bytes.to_array()) -} - -// Helper function to convert a BytesN<64> to ed25519_dalek::Signature -fn convert_signature_to_dalek(signature: &BytesN<64>) -> Result { - Signature::from_bytes(&signature.to_array()) -} \ No newline at end of file diff --git a/contracts/soroban/contracts/cluster-connection/src/test.rs b/contracts/soroban/contracts/cluster-connection/src/test.rs index 0e41564c..3f7e978a 100644 --- a/contracts/soroban/contracts/cluster-connection/src/test.rs +++ b/contracts/soroban/contracts/cluster-connection/src/test.rs @@ -15,7 +15,7 @@ use crate::{ use soroban_sdk::{ symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, - token, vec, Address, Bytes, Env, IntoVal, String, Symbol, + token, vec, Address, Bytes, Env, IntoVal, String, Symbol, Vec, }; pub struct TestContext { @@ -343,3 +343,117 @@ fn test_get_receipt_returns_false() { 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 validator = Address::generate(&ctx.env); + client.add_validator(&validator.clone()); + + let mut validators = Vec::new(&ctx.env); + validators.push_back(validator.clone()); + + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.relayer, + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + client.address.clone(), + Symbol::new(&ctx.env, "add_validator"), + (validator.clone(),).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + + let validator = Address::generate(&ctx.env); + client.add_validator(&validator.clone()); + validators.push_back(validator.clone()); + + + assert_eq!( + client.get_validators(), + validators + ); +} + +#[test] +fn test_remove_validator() { + let ctx = TestContext::default(); + let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract); + + ctx.init_context(&client); + + let validator1 = Address::generate(&ctx.env); + let validator2 = Address::generate(&ctx.env); + let validator3 = Address::generate(&ctx.env); + + client.add_validator(&validator1.clone()); + client.add_validator(&validator2.clone()); + client.add_validator(&validator3.clone()); + + let mut validators = Vec::new(&ctx.env); + validators.push_back(validator1.clone()); + validators.push_back(validator2.clone()); + validators.push_back(validator3.clone()); + + assert_eq!( + client.get_validators(), + validators + ); + + assert_eq!( + client.get_validators().len(), + 3 + ); + + client.remove_validator(&validator2.clone()); + + assert_eq!( + client.get_validators().len(), + 2 + ); + validators.remove(1); + + 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 threshold: u32 = 2_u32; + client.set_validators_threshold(&threshold); + + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.relayer, + 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); +} diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test.rs index df83e062..6be6e775 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/mock-dapp-multi/src/test/setup.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs index ccaa3877..ca753e50 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs @@ -72,6 +72,7 @@ impl TestContext { native_token: self.native_token.clone(), network_id: self.nid.clone(), sender: Address::generate(&self.env), + upgrade_authority: Address::generate(&self.env), }; xcall_client.initialize(&initialize_msg); @@ -86,6 +87,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: Address::generate(&self.env), xcall_address: self.xcall.clone(), + upgrade_authority: Address::generate(&self.env), }; connection_client.initialize(&initialize_msg); diff --git a/contracts/soroban/contracts/xcall/src/test/setup.rs b/contracts/soroban/contracts/xcall/src/test/setup.rs index e4591430..6c5aff05 100644 --- a/contracts/soroban/contracts/xcall/src/test/setup.rs +++ b/contracts/soroban/contracts/xcall/src/test/setup.rs @@ -160,6 +160,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: self.admin.clone(), xcall_address: self.contract.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); From 53bcc9c8840745cf64cfb76cdae2a187a4fea995 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Thu, 17 Oct 2024 16:43:55 +0545 Subject: [PATCH 38/75] fix: cluster connection spec change --- .../adapter/cluster/ClusterConnection.java | 165 ++++++++++++------ .../cluster/ClusterConnectionTest.java | 94 ++++------ 2 files changed, 138 insertions(+), 121 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index bb440c38..f60f0e4e 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -21,6 +21,7 @@ import score.Address; import score.BranchDB; +import score.ByteArrayObjectWriter; import score.DictDB; import score.VarDB; import score.ArrayDB; @@ -35,8 +36,9 @@ public class ClusterConnection { protected final VarDB
xCall = Context.newVarDB("callService", Address.class); - protected final VarDB
adminAddress = Context.newVarDB("relayer", Address.class); - protected final VarDB reqValidatorCnt = Context.newVarDB("reqValidatorCnt", BigInteger.class); + protected final VarDB
adminAddress = Context.newVarDB("admin", Address.class); + protected final VarDB
relayerAddress = Context.newVarDB("relayer", Address.class); + protected final VarDB validatorsThreshold = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); @@ -44,16 +46,12 @@ public class ClusterConnection { protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); protected final BranchDB> receipts = Context.newBranchDB("receipts", Boolean.class); - private final DictDB validatorsLookup = Context.newDictDB("validatorsLookup", Boolean.class); - public ClusterConnection(Address _relayer, Address _xCall) { if (xCall.get() == null) { xCall.set(_xCall); - adminAddress.set(_relayer); + adminAddress.set(Context.getCaller()); + relayerAddress.set(_relayer); connSn.set(BigInteger.ZERO); - validators.add(_relayer); - validatorsLookup.set(_relayer,true); - ValidatorAdded(_relayer); } } @@ -71,57 +69,87 @@ public Address[] listValidators() { return sgs; } +/** + * Adds a list of validators and sets the validation threshold. + * + * Clears existing validators and adds the provided addresses as validators. + * Ensures that the caller is an admin and that the number of validators + * meets or exceeds the specified threshold. + * + * @param _validators an array of addresses to be added as validators + * @param _threshold the minimum required number of validators + * @throws Exception if the number of validators is less than the threshold + */ @External - public void addValidator(Address _validator) { + public void addValidator(Address[] _validators, BigInteger _threshold) { OnlyAdmin(); - Context.require(validatorsLookup.get(_validator)==null,"Validator already exists"); - validators.add(_validator); - validatorsLookup.set(_validator,true); - ValidatorAdded(_validator); + clearValidators(); + for (Address validator : _validators) { + if(!isValidator(validator)) { + validators.add(validator); + } + } + Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); + validatorsThreshold.set(_threshold); + ValidatorSetAdded(_validators, _threshold); } - @External - public void removeValidator(Address _validator) { - OnlyAdmin(); - Context.require(_validator != adminAddress.get(),"cannot remove admin"); - Context.require(validatorsLookup.get(_validator)!=null,"Validator doesn't exists"); - Context.require((this.validators.size() - 1) >= reqValidatorCnt.get().intValue(),"Validator size less than required count after removal"); - Address top = this.validators.pop(); - if (!top.equals(_validator)) { - for (int i = 0; i < this.validators.size(); i++) { - if (_validator.equals(this.validators.get(i))) { - this.validators.set(i, top); - break; - } - } - validatorsLookup.set(_validator,null); - ValidatorRemoved(_validator); + /** + * Clear the current validators. + * + * This is a private helper method called by addValidator. + */ + private void clearValidators() { + for(int i = 0; i < validators.size(); i++) { + validators.set(i, null); } + } +/** + * Checks if the provided address is a validator. + * + * @param validator the address to check for validation + * @return true if the address is a validator, false otherwise + */ + @External(readonly = true) + public boolean isValidator(Address validator) { + for(int i = 0; i < validators.size(); i++) { + if(validator.equals(validators.get(i))) { + return true; + } + } + return false; } @EventLog(indexed = 2) public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } - @EventLog(indexed = 1) - public void ValidatorAdded(Address _validator) { - } - - @EventLog(indexed = 1) - public void ValidatorRemoved(Address _validator) { + @EventLog(indexed = 0) + public void ValidatorSetAdded(Address[] _validators, BigInteger _threshold) { } /** - * Sets the admin address. + * Sets the relayer address. * * @param _relayer the new admin address */ @External - public void setAdmin(Address _relayer) { + public void setRelayer(Address _relayer) { OnlyAdmin(); - adminAddress.set(_relayer); - } + relayerAddress.set(_relayer); + } + + /** + * Sets the admin address. + * + * @param _admin the new admin address + */ + @External + public void setAdmin(Address _admin) { + OnlyAdmin(); + adminAddress.set(_admin); + } /** * Retrieves the admin address. @@ -141,7 +169,7 @@ public Address admin() { @External public void setRequiredValidatorCount(BigInteger _validatorCnt) { OnlyAdmin(); - reqValidatorCnt.set(_validatorCnt); + validatorsThreshold.set(_validatorCnt); } /** @@ -151,7 +179,7 @@ public void setRequiredValidatorCount(BigInteger _validatorCnt) { */ @External(readonly = true) public BigInteger requiredValidatorCount() { - return reqValidatorCnt.get(); + return validatorsThreshold.get(); } /** @@ -163,7 +191,7 @@ public BigInteger requiredValidatorCount() { */ @External public void setFee(String networkId, BigInteger messageFee, BigInteger responseFee) { - OnlyAdmin(); + OnlyRelayer(); messageFees.set(networkId, messageFee); responseFees.set(networkId, responseFee); } @@ -223,17 +251,18 @@ public void sendMessage(String to, String svc, BigInteger sn, byte[] msg) { @External public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byte[] msg, byte[][] signatures) { - OnlyAdmin(); - Context.require(signatures.length >= reqValidatorCnt.get().intValue(), "Not enough signatures"); + OnlyRelayer(); + Context.require(signatures.length >= validatorsThreshold.get().intValue(), "Not enough signatures"); + byte[] messageHash = getMessageHash(srcNetwork, _connSn, msg); List
uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address validator = getValidator(msg, signature); - Context.require(validatorsLookup.get(validator)!=null, "Invalid signature provided"); + Address validator = getValidator(messageHash, signature); + Context.require(isValidator(validator), "Invalid signature provided"); if (!uniqueValidators.contains(validator)) { uniqueValidators.add(validator); } } - Context.require(uniqueValidators.size() >= reqValidatorCnt.get().intValue(), "Not enough valid signatures"); + Context.require(uniqueValidators.size() >= validatorsThreshold.get().intValue(), "Not enough valid signatures"); recvMessage(srcNetwork, _connSn, msg); } @@ -246,22 +275,17 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt */ @External public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { - OnlyAdmin(); + OnlyRelayer(); Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); receipts.at(srcNetwork).set(_connSn, true); Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } private Address getValidator(byte[] msg, byte[] sig){ - byte[] hashMessage = getHash(msg); - byte[] key = Context.recoverKey("ecdsa-secp256k1", hashMessage, sig, true); + byte[] key = Context.recoverKey("ecdsa-secp256k1", msg, sig, true); return Context.getAddressFromKey(key); } - private byte[] getHash(byte[] msg){ - return Context.hash("keccak-256", msg); - } - /** * Reverts a message. * @@ -270,7 +294,7 @@ private byte[] getHash(byte[] msg){ */ @External public void revertMessage(BigInteger sn) { - OnlyAdmin(); + OnlyRelayer(); Context.call(xCall.get(), "handleError", sn); } @@ -280,8 +304,8 @@ public void revertMessage(BigInteger sn) { */ @External public void claimFees() { - OnlyAdmin(); - Context.transfer(admin(), Context.getBalance(Context.getAddress())); + OnlyRelayer(); + Context.transfer(relayerAddress.get(), Context.getBalance(Context.getAddress())); } /** @@ -296,6 +320,15 @@ public boolean getReceipts(String srcNetwork, BigInteger _connSn) { return receipts.at(srcNetwork).getOrDefault(_connSn, false); } + /** + * Checks if the caller of the function is the admin. + * + * @return true if the caller is the admin, false otherwise + */ + private void OnlyRelayer() { + Context.require(Context.getCaller().equals(relayerAddress.get()), "Only relayer can call this function"); + } + /** * Checks if the caller of the function is the admin. * @@ -305,4 +338,22 @@ private void OnlyAdmin() { Context.require(Context.getCaller().equals(adminAddress.get()), "Only admin can call this function"); } + /** + * Gets the hash of a message. + * + * @param srcNetwork the source network id + * @param _connSn the serial number of connection message + * @param msg the message to hash + * @return the hash of the message + */ + private byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[] msg) { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + writer.beginList(3); + writer.write(srcNetwork); + writer.write(_connSn); + writer.write(msg); + writer.end(); + return Context.hash("keccak-256", writer.toByteArray()); + } + } \ No newline at end of file diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index fd021c14..914272bc 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -2,31 +2,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.AdditionalMatchers.aryEq; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.security.*; -import java.beans.Transient; import java.math.BigInteger; import score.Context; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jcajce.provider.digest.Keccak; import foundation.icon.icx.KeyWallet; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.function.Executable; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -35,21 +26,12 @@ import com.iconloop.score.test.ServiceManager; import com.iconloop.score.test.TestBase; -import xcall.adapter.cluster.ClusterConnection; import score.UserRevertedException; import score.Address; -import foundation.icon.ee.types.Bytes; -import foundation.icon.icx.Call; -import foundation.icon.score.client.RevertedException; -import foundation.icon.xcall.CSMessage; -import foundation.icon.xcall.CSMessageRequest; +import score.ByteArrayObjectWriter; import foundation.icon.xcall.CallService; -import foundation.icon.xcall.CallServiceReceiver; import foundation.icon.xcall.CallServiceScoreInterface; -import foundation.icon.xcall.ConnectionScoreInterface; -import foundation.icon.xcall.Connection; -import foundation.icon.xcall.NetworkAddress; -import s.java.math.BigDecimal; + import xcall.icon.test.MockContract; @@ -90,7 +72,7 @@ public void setup() throws Exception { @Test public void testSetAdmin() { - connection.invoke(source_relayer, "setAdmin", admin.getAddress()); + connection.invoke(owner, "setAdmin", admin.getAddress()); assertEquals(connection.call("admin"), admin.getAddress()); } @@ -123,7 +105,7 @@ public void testRecvMessage() { public void testRecvMessage_unauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @Test @@ -151,7 +133,7 @@ public void testRevertMessage() { @Test public void testRevertMessage_unauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(user, "revertMessage", BigInteger.ONE)); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @@ -159,7 +141,7 @@ public void testRevertMessage_unauthorized(){ public void testSetFeesUnauthorized(){ UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "setFee", "0xevm", BigInteger.TEN, BigInteger.TEN)); - assertEquals("Reverted(0): "+"Only admin can call this function", e.getMessage()); + assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); } @Test @@ -189,7 +171,7 @@ public void testClaimFees(){ public void testClaimFees_unauthorized(){ setFee(); UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(user, "claimFees")); - assertEquals(e.getMessage(), "Reverted(0): "+"Only admin can call this function"); + assertEquals(e.getMessage(), "Reverted(0): "+"Only relayer can call this function"); } public MockedStatic.Verification value() { @@ -210,12 +192,12 @@ public void testGetReceipt(){ @Test public void testRecvMessageWithSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.ONE); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -223,15 +205,14 @@ public void testRecvMessageWithSignatures() throws Exception{ @Test public void testRecvMessageWithMultiSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); byte[][] byteArray = new byte[2][]; KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet2.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -239,12 +220,12 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ @Test public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -254,13 +235,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ @Test public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Exception{ byte[] data = "test".getBytes(); - byte[] messageHash = keccak256(data); + byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); byte[][] byteArray = new byte[2][]; byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); + Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); @@ -268,37 +249,22 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep } - public static byte[] keccak256(byte[] input) { - Keccak.Digest256 keccak256 = new Keccak.Digest256(); - return keccak256.digest(input); + public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[] msg) { + ByteArrayObjectWriter writer = Context.newByteArrayObjectWriter("RLPn"); + writer.beginList(3); + writer.write(srcNetwork); + writer.write(_connSn); + writer.write(msg); + writer.end(); + return Context.hash("keccak-256", writer.toByteArray()); } - @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); + Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; + connection.invoke(owner, "addValidator", validators, BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } - @Test - public void testAddNRemoveSigners() throws Exception{ - KeyWallet wallet = KeyWallet.create(); - KeyWallet wallet3 = KeyWallet.create(); - connection.invoke(source_relayer, "addValidator", Address.fromString(wallet.getAddress().toString())); - connection.invoke(source_relayer, "setRequiredValidatorCount", BigInteger.TWO); - Address[] signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 2); - - UserRevertedException e = assertThrows(UserRevertedException.class, - ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet3.getAddress().toString()))); - assertEquals("Reverted(0): Validator doesn't exists", e.getMessage()); - - UserRevertedException ex = assertThrows(UserRevertedException.class, - ()-> connection.invoke(source_relayer, "removeValidator", Address.fromString(wallet.getAddress().toString()))); - assertEquals("Reverted(0): Validator size less than required count after removal", ex.getMessage()); - signers = connection.call(Address[].class,"listValidators"); - assertEquals(signers.length, 2); - } - } \ No newline at end of file From 0b14023c49530a66543fc8952f66d991cd269221 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Thu, 17 Oct 2024 17:14:43 +0545 Subject: [PATCH 39/75] fix: cluster connection spec change --- .../contracts/adapters/ClusterConnection.sol | 110 +++++++++++------- .../evm/test/adapters/ClusterConnection.t.sol | 79 ++++++------- 2 files changed, 102 insertions(+), 87 deletions(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index f9aee6e8..a0eb7e43 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -6,60 +6,66 @@ import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.s import "@xcall/utils/Types.sol"; import "@xcall/contracts/xcall/interfaces/IConnection.sol"; import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; +import "@iconfoundation/xcall-solidity-library/utils/RLPEncode.sol"; contract ClusterConnection is Initializable, IConnection { + + using RLPEncode for bytes; + using RLPEncode for string; + using RLPEncode for uint256; + mapping(string => uint256) private messageFees; mapping(string => uint256) private responseFees; mapping(string => mapping(uint256 => bool)) receipts; - mapping(address => bool) public isValidator; address private xCall; + address private relayerAddress; address private adminAddress; uint256 public connSn; address[] private validators; - uint8 private reqValidatorCnt; + uint8 private validatorsThreshold; event Message(string targetNetwork, uint256 sn, bytes _msg); - event ValidatorAdded(address _validator); - event ValidatorRemoved(address _validator); + event ValidatorSetAdded(address[] _validator, uint8 _threshold); + + modifier onlyRelayer() { + require(msg.sender == this.relayer(), "OnlyRelayer"); + _; + } modifier onlyAdmin() { - require(msg.sender == this.admin(), "OnlyRelayer"); + require(msg.sender == this.admin(), "OnlyAdmin"); _; } function initialize(address _relayer, address _xCall) public initializer { xCall = _xCall; - validators.push(_relayer); - adminAddress = _relayer; - emit ValidatorAdded(_relayer); + adminAddress = msg.sender; + relayerAddress = _relayer; } function listValidators() external view returns (address[] memory) { return validators; } - function addValidator(address _validator) external onlyAdmin { - require(!isValidator[_validator], "Address is already an signer"); - validators.push(_validator); - isValidator[_validator] = true; - emit ValidatorAdded(_validator); + function setValidators(address[] memory _validators, uint8 _threshold) external onlyAdmin { + delete validators; + for (uint i = 0; i < _validators.length; i++) { + if(!isValidator(_validators[i]) && _validators[i] != address(0)) { + validators.push(_validators[i]); + } + } + require(validators.length >= _threshold, "Not enough validators"); + validatorsThreshold = _threshold; + emit ValidatorSetAdded(_validators, _threshold); } - function removeValidator(address _validator) external onlyAdmin { - require(_validator!=this.admin(), "Cannot remove admin"); - require(isValidator[_validator],"Validator doesn't exist"); - require(validators.length-1>=reqValidatorCnt,"Validator size less than required count after removal"); + function isValidator(address signer) public view returns (bool) { for (uint i = 0; i < validators.length; i++) { - if (validators[i] == _validator) { - validators[i] = validators[validators.length - 1]; - validators.pop(); - isValidator[_validator] = false; - break; + if (validators[i] == signer) { + return true; } - emit ValidatorRemoved(_validator); } - } /** @@ -72,7 +78,7 @@ contract ClusterConnection is Initializable, IConnection { string calldata networkId, uint256 messageFee, uint256 responseFee - ) external onlyAdmin { + ) external onlyRelayer { messageFees[networkId] = messageFee; responseFees[networkId] = responseFee; } @@ -99,13 +105,13 @@ contract ClusterConnection is Initializable, IConnection { @notice Sends the message to a specific network. @param sn : positive for two-way message, zero for one-way message, negative for response @param to String ( Network Id of destination network ) - @param svc String ( name of the service ) + @param _svc String ( name of the service ) @param sn Integer ( serial number of the xcall message ) @param _msg Bytes ( serialized bytes of Service Message ) */ function sendMessage( string calldata to, - string calldata svc, + string calldata _svc, int256 sn, bytes calldata _msg ) external payable override { @@ -132,10 +138,9 @@ contract ClusterConnection is Initializable, IConnection { uint256 _connSn, bytes calldata _msg, bytes[] calldata _signedMessages - ) public onlyAdmin { - require(_signedMessages.length > 0, "No signatures provided"); - require(_signedMessages.length >= reqValidatorCnt, "Not enough signatures passed"); - bytes32 messageHash = keccak256(_msg); + ) public onlyRelayer { + require(_signedMessages.length >= validatorsThreshold, "Not enough signatures passed"); + bytes32 messageHash = getMessageHash(srcNetwork, _connSn, _msg); uint signerCount = 0; address[] memory collectedSigners = new address[](_signedMessages.length); for (uint i = 0; i < _signedMessages.length; i++) { @@ -146,7 +151,7 @@ contract ClusterConnection is Initializable, IConnection { signerCount++; } } - require(signerCount >= reqValidatorCnt,"Not enough valid signatures passed"); + require(signerCount >= validatorsThreshold,"Not enough valid signatures passed"); recvMessage(srcNetwork,_connSn,_msg); } @@ -191,7 +196,7 @@ contract ClusterConnection is Initializable, IConnection { string memory srcNetwork, uint256 _connSn, bytes calldata _msg - ) public onlyAdmin { + ) public onlyRelayer { require(!receipts[srcNetwork][_connSn], "Duplicate Message"); receipts[srcNetwork][_connSn] = true; ICallService(xCall).handleMessage(srcNetwork, _msg); @@ -201,15 +206,15 @@ contract ClusterConnection is Initializable, IConnection { @notice Sends the balance of the contract to the owner(relayer) */ - function claimFees() public onlyAdmin { - payable(adminAddress).transfer(address(this).balance); + function claimFees() public onlyRelayer { + payable(relayerAddress).transfer(address(this).balance); } /** @notice Revert a messages, used in special cases where message can't just be dropped @param sn Integer ( serial number of the xcall message ) */ - function revertMessage(uint256 sn) public onlyAdmin { + function revertMessage(uint256 sn) public onlyRelayer { ICallService(xCall).handleError(sn); } @@ -227,17 +232,25 @@ contract ClusterConnection is Initializable, IConnection { } /** - @notice Set the address of the admin. - @param _address The address of the admin. + @notice Set the address of the relayer. + @param _address The address of the relayer. */ - function setAdmin(address _address) external onlyAdmin { + function setAdmin(address _address) external onlyRelayer { adminAddress = _address; } /** - @notice Gets the address of admin - @return (Address) the address of admin + @notice Gets the address of relayer + @return (Address) the address of relayer */ + function relayer() external view returns (address) { + return relayerAddress; + } + + /** + @notice Gets the address of admin + @return (Address) the address of admin + */ function admin() external view returns (address) { return adminAddress; } @@ -246,11 +259,20 @@ contract ClusterConnection is Initializable, IConnection { @notice Set the required signature count for verification. @param _count The desired count. */ - function setRequiredValidatorCount(uint8 _count) external onlyAdmin { - reqValidatorCnt = _count; + function setRequiredValidatorCount(uint8 _count) external onlyAdmin() { + validatorsThreshold = _count; } function getRequiredValidatorCount() external view returns (uint8) { - return reqValidatorCnt; + return validatorsThreshold; + } + + function getMessageHash(string memory srcNetwork, uint256 _connSn, bytes calldata _msg) internal pure returns (bytes32) { + bytes memory rlp = abi.encodePacked( + srcNetwork.encodeString(), + _connSn.encodeUint(), + _msg.encodeBytes() + ); + return keccak256(rlp); } } diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index e45262e5..57e4754c 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -7,8 +7,13 @@ import "@xcall/contracts/adapters/ClusterConnection.sol"; import "@xcall/contracts/xcall/CallService.sol"; import "@xcall/contracts/mocks/multi-protocol-dapp/MultiProtocolSampleDapp.sol"; import "@xcall/utils/Types.sol"; +import "@iconfoundation/xcall-solidity-library/utils/RLPEncode.sol"; contract ClusterConnectionTest is Test { + + using RLPEncode for bytes; + using RLPEncode for string; + using RLPEncode for uint256; using RLPEncodeStruct for Types.CSMessage; using RLPEncodeStruct for Types.CSMessageRequestV2; @@ -335,13 +340,17 @@ contract ClusterConnectionTest is Test { uint256 pk2 = hexStringToUint256("47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"); uint256 pk3 = hexStringToUint256("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); uint256 pk4 = hexStringToUint256("2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"); - bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); + bytes32 hash = getMessageHash(nidSource, 1, RLPEncodeStruct.encodeCSMessage(message)); + vm.startPrank(owner); + address[] memory validators = new address[](4); + validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + validators[1] = address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65); + validators[2] = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + validators[3] = address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720); + adapterTarget.setValidators(validators, 4); + vm.stopPrank(); + vm.startPrank(destination_relayer); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addValidator(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); - adapterTarget.addValidator(address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8)); - adapterTarget.addValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); - adapterTarget.setRequiredValidatorCount(4); vm.expectEmit(); emit CallMessage(iconDapp, ParseAddress.toString(address(dappSource)), 1, 1, data); vm.expectCall(address(xCallTarget), abi.encodeCall(xCallTarget.handleMessage, (nidSource,RLPEncodeStruct.encodeCSMessage(message)))); @@ -391,47 +400,19 @@ contract ClusterConnectionTest is Test { } function testAddValidator() public { - vm.startPrank(destination_relayer); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - assertEq(2, adapterTarget.listValidators().length); - vm.stopPrank(); - } + vm.startPrank(owner); - function testRemoveValidator() public { - vm.startPrank(destination_relayer); - adapterTarget.setRequiredValidatorCount(2); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - assertEq(3, adapterTarget.listValidators().length); - adapterTarget.removeValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); + address[] memory validators = new address[](2); + validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + validators[1] = address(0x976EA74026E726554dB657fA54763abd0C3a0aa9); + adapterTarget.setValidators(validators, 2); assertEq(2, adapterTarget.listValidators().length); vm.stopPrank(); } - function testRemoveNonExistentValidator() public { - vm.startPrank(destination_relayer); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - assertEq(3, adapterTarget.listValidators().length); - vm.expectRevert("Validator doesn't exist"); - adapterTarget.removeValidator(address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720)); - vm.stopPrank(); - } - - function testRemoveValidatorSize() public { - vm.startPrank(destination_relayer); - adapterTarget.setRequiredValidatorCount(3); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - assertEq(3, adapterTarget.listValidators().length); - vm.expectRevert("Validator size less than required count after removal"); - adapterTarget.removeValidator(address(0x976EA74026E726554dB657fA54763abd0C3a0aa9)); - vm.stopPrank(); - } - function testRequiredCount() public { - vm.startPrank(destination_relayer); + vm.startPrank(owner); adapterTarget.setRequiredValidatorCount(3); assertEq(3, adapterTarget.getRequiredValidatorCount()); vm.stopPrank(); @@ -457,10 +438,13 @@ contract ClusterConnectionTest is Test { ); uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); + vm.startPrank(owner); + address[] memory validators = new address[](2); + validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + validators[1] = address(0x976EA74026E726554dB657fA54763abd0C3a0aa9); + adapterTarget.setValidators(validators, 2); + vm.stopPrank(); vm.startPrank(destination_relayer); - adapterTarget.addValidator(address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266)); - adapterTarget.addValidator(address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65)); - adapterTarget.setRequiredValidatorCount(2); vm.expectRevert("Not enough valid signatures passed"); bytes[] memory signatures = new bytes[](2) ; signatures[0] = signMessage(pk,hash); @@ -473,4 +457,13 @@ contract ClusterConnectionTest is Test { ); vm.stopPrank(); } + + function getMessageHash(string memory srcNetwork, uint256 _connSn, bytes memory _msg) internal pure returns (bytes32) { + bytes memory rlp = abi.encodePacked( + srcNetwork.encodeString(), + _connSn.encodeUint(), + _msg.encodeBytes() + ); + return keccak256(rlp); + } } From 80c53bcb29f20a98436d9aa294cc4dde11f6ab45 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Thu, 17 Oct 2024 17:21:26 +0545 Subject: [PATCH 40/75] fix: event data type changed --- .../main/java/xcall/adapter/cluster/ClusterConnection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index f60f0e4e..bf8103ed 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -91,7 +91,7 @@ public void addValidator(Address[] _validators, BigInteger _threshold) { } Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); validatorsThreshold.set(_threshold); - ValidatorSetAdded(_validators, _threshold); + ValidatorSetAdded(_validators.toString(), _threshold); } /** @@ -126,7 +126,7 @@ public void Message(String targetNetwork, BigInteger connSn, byte[] msg) { } @EventLog(indexed = 0) - public void ValidatorSetAdded(Address[] _validators, BigInteger _threshold) { + public void ValidatorSetAdded(String _validators, BigInteger _threshold) { } /** From 178531769d2fd7f45192dc284d2c847816768fa9 Mon Sep 17 00:00:00 2001 From: bishalbikram Date: Fri, 18 Oct 2024 12:28:11 +0545 Subject: [PATCH 41/75] fix: change proxy request account seeds derivation parameters --- .../solana/libs/xcall-lib/src/xcall_type.rs | 6 +++ .../centralized-connection/src/helper.rs | 2 + .../src/instructions/query_accounts.rs | 20 +++++--- .../centralized-connection/src/lib.rs | 2 +- .../instructions/execute_forced_rollback.rs | 5 +- .../programs/mock-dapp-multi/src/lib.rs | 5 +- .../programs/mock-dapp-multi/src/xcall.rs | 14 +++++- contracts/solana/programs/xcall/src/event.rs | 2 + .../xcall/src/instructions/execute_call.rs | 4 +- .../instructions/handle_forced_rollback.rs | 4 +- .../xcall/src/instructions/handle_message.rs | 48 ++++++++++++++----- .../xcall/src/instructions/query_accounts.rs | 24 ++++++++-- contracts/solana/programs/xcall/src/lib.rs | 44 +++++++++++++++-- .../centralized-connection.ts | 40 ++++++++++++++-- .../tests/centralized-connection/setup.ts | 3 +- contracts/solana/tests/xcall/setup.ts | 42 +++++++++++++--- 16 files changed, 215 insertions(+), 50 deletions(-) diff --git a/contracts/solana/libs/xcall-lib/src/xcall_type.rs b/contracts/solana/libs/xcall-lib/src/xcall_type.rs index 3ec5cf07..aaececfa 100644 --- a/contracts/solana/libs/xcall-lib/src/xcall_type.rs +++ b/contracts/solana/libs/xcall-lib/src/xcall_type.rs @@ -25,12 +25,14 @@ pub struct HandleMessageArgs { pub from_nid: String, pub message: Vec, pub sequence_no: u128, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] pub struct HandleRequestArgs { pub from_nid: String, pub msg_payload: Vec, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] @@ -38,6 +40,7 @@ pub struct HandleResultArgs { pub from_nid: String, pub msg_payload: Vec, pub sequence_no: u128, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] @@ -48,4 +51,7 @@ pub struct HandleErrorArgs { #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] pub struct HandleForcedRollback { pub req_id: u128, + pub from_nid: String, + pub conn_sn: u128, + pub connection: Pubkey, } diff --git a/contracts/solana/programs/centralized-connection/src/helper.rs b/contracts/solana/programs/centralized-connection/src/helper.rs index 927c17a5..96ead7ab 100644 --- a/contracts/solana/programs/centralized-connection/src/helper.rs +++ b/contracts/solana/programs/centralized-connection/src/helper.rs @@ -46,12 +46,14 @@ pub fn call_xcall_handle_message<'info>( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_type::HandleMessageArgs { from_nid, message, sequence_no, + conn_sn, }; args.serialize(&mut data)?; diff --git a/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs b/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs index 7f149623..9f21112f 100644 --- a/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs +++ b/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs @@ -2,7 +2,7 @@ use anchor_lang::{ prelude::*, solana_program::{ instruction::Instruction, - program::{get_return_data, invoke}, + program::{get_return_data, invoke, invoke_signed}, system_program, }, }; @@ -35,8 +35,8 @@ pub fn query_send_message_accounts<'info>( }) } -pub fn query_recv_message_accounts( - ctx: Context, +pub fn query_recv_message_accounts<'info>( + ctx: Context<'_, '_, '_, 'info, QueryAccountsCtx<'info>>, src_network: String, conn_sn: u128, msg: Vec, @@ -65,8 +65,8 @@ pub fn query_recv_message_accounts( AccountMetadata::new(authority, false), ]; - let mut xcall_account_metas = vec![]; - let mut xcall_account_infos = vec![]; + let mut xcall_account_metas = vec![AccountMeta::new_readonly(config.key(), true)]; + let mut xcall_account_infos = vec![config.to_account_info()]; for (_, account) in ctx.remaining_accounts.iter().enumerate() { if account.is_writable { @@ -78,7 +78,7 @@ pub fn query_recv_message_accounts( xcall_account_infos.push(account.to_account_info()) } - let ix_data = get_handle_message_ix_data(src_network, msg, sequence_no)?; + let ix_data = get_handle_message_ix_data(src_network, msg, sequence_no, conn_sn)?; let ix = Instruction { program_id: config.xcall, @@ -86,7 +86,11 @@ pub fn query_recv_message_accounts( data: ix_data, }; - invoke(&ix, &xcall_account_infos)?; + invoke_signed( + &ix, + &xcall_account_infos, + &[&[Config::SEED_PREFIX.as_bytes(), &[config.bump]]], + )?; let (_, data) = get_return_data().unwrap(); let mut data_slice: &[u8] = &data; @@ -171,12 +175,14 @@ pub fn get_handle_message_ix_data( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result> { let mut ix_args_data = vec![]; let ix_args = xcall_type::HandleMessageArgs { from_nid, message, sequence_no, + conn_sn, }; ix_args.serialize(&mut ix_args_data)?; diff --git a/contracts/solana/programs/centralized-connection/src/lib.rs b/contracts/solana/programs/centralized-connection/src/lib.rs index 7bb0ac67..a7755eef 100644 --- a/contracts/solana/programs/centralized-connection/src/lib.rs +++ b/contracts/solana/programs/centralized-connection/src/lib.rs @@ -72,7 +72,7 @@ pub mod centralized_connection { msg: Vec, sequence_no: u128, ) -> Result<()> { - helper::call_xcall_handle_message(ctx, src_network, msg, sequence_no) + helper::call_xcall_handle_message(ctx, src_network, msg, sequence_no, conn_sn) } pub fn revert_message<'info>( diff --git a/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs b/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs index 3fe45ae7..7bd63c64 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs @@ -6,8 +6,11 @@ use crate::{state::*, xcall}; pub fn execute_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { - let ix_data = xcall::get_handle_forced_rollback_ix_data(req_id)?; + let ix_data = xcall::get_handle_forced_rollback_ix_data(req_id, from_nid, conn_sn, connection)?; xcall::call_xcall_handle_forced_rollback( &ix_data, diff --git a/contracts/solana/programs/mock-dapp-multi/src/lib.rs b/contracts/solana/programs/mock-dapp-multi/src/lib.rs index c56b9bb8..2b0f3a86 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/lib.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/lib.rs @@ -64,8 +64,11 @@ pub mod mock_dapp_multi { pub fn execute_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { - instructions::execute_forced_rollback(ctx, req_id) + instructions::execute_forced_rollback(ctx, req_id, from_nid, conn_sn, connection) } #[allow(unused_variables)] diff --git a/contracts/solana/programs/mock-dapp-multi/src/xcall.rs b/contracts/solana/programs/mock-dapp-multi/src/xcall.rs index d4cc32f7..7fd367fd 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/xcall.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/xcall.rs @@ -100,9 +100,19 @@ pub fn get_send_call_ix_data(msg: Vec, to: NetworkAddress) -> Result Ok(ix_data) } -pub fn get_handle_forced_rollback_ix_data(req_id: u128) -> Result> { +pub fn get_handle_forced_rollback_ix_data( + req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, +) -> Result> { let mut ix_args_data = vec![]; - let ix_args = xcall_type::HandleForcedRollback { req_id }; + let ix_args = xcall_type::HandleForcedRollback { + req_id, + from_nid, + conn_sn, + connection, + }; ix_args.serialize(&mut ix_args_data)?; let ix_data = helpers::get_instruction_data(HANDLE_FORCED_ROLLBACK_IX, ix_args_data); diff --git a/contracts/solana/programs/xcall/src/event.rs b/contracts/solana/programs/xcall/src/event.rs index c2190054..3d4bf653 100644 --- a/contracts/solana/programs/xcall/src/event.rs +++ b/contracts/solana/programs/xcall/src/event.rs @@ -16,6 +16,8 @@ pub struct CallMessage { pub sn: u128, pub reqId: u128, pub data: Vec, + pub connection: Pubkey, + pub connSn: u128, } #[event] diff --git a/contracts/solana/programs/xcall/src/instructions/execute_call.rs b/contracts/solana/programs/xcall/src/instructions/execute_call.rs index 1ea976b7..32935034 100644 --- a/contracts/solana/programs/xcall/src/instructions/execute_call.rs +++ b/contracts/solana/programs/xcall/src/instructions/execute_call.rs @@ -96,7 +96,7 @@ pub fn execute_call<'info>( } #[derive(Accounts)] -#[instruction(req_id : u128)] +#[instruction(req_id : u128, from_nid: String, conn_sn: u128, connection: Pubkey)] pub struct ExecuteCallCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -125,7 +125,7 @@ pub struct ExecuteCallCtx<'info> { /// calls. The account is closed after use, with any remaining funds sent to the `admin`. #[account( mut, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump, close = admin )] diff --git a/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs b/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs index 19b02b37..424ec86d 100644 --- a/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs +++ b/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs @@ -77,7 +77,7 @@ pub fn handle_forced_rollback<'info>( } #[derive(Accounts)] -#[instruction(req_id: u128)] +#[instruction(req_id: u128, from_nid: String, conn_sn: u128, connection: Pubkey)] pub struct HandleForcedRollbackCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable because /// it will be debited for any fees or rent required during the transaction. @@ -111,7 +111,7 @@ pub struct HandleForcedRollbackCtx<'info> { /// calls and is closed after use, with any remaining funds sent to the `admin`. #[account( mut, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump, close = admin )] diff --git a/contracts/solana/programs/xcall/src/instructions/handle_message.rs b/contracts/solana/programs/xcall/src/instructions/handle_message.rs index 562ea80e..7450194b 100644 --- a/contracts/solana/programs/xcall/src/instructions/handle_message.rs +++ b/contracts/solana/programs/xcall/src/instructions/handle_message.rs @@ -26,6 +26,8 @@ use crate::{ /// - `message`: The encoded message payload received from the chain. /// - `sequence_no`: The sequence number associated with the message, used to track message /// ordering and responses. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if successful, or an appropriate error if any validation or @@ -35,6 +37,7 @@ pub fn handle_message<'info>( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let config = &ctx.accounts.config; if config.network_id == from_nid.to_string() { @@ -48,7 +51,7 @@ pub fn handle_message<'info>( return Err(XcallError::PendingResponseAccountMustNotBeSpecified.into()); } - invoke_handle_request(ctx, from_nid, cs_message.payload)? + invoke_handle_request(ctx, from_nid, cs_message.payload, conn_sn)? } CSMessageType::CSMessageResult => { let rollback_account = ctx @@ -67,7 +70,7 @@ pub fn handle_message<'info>( return Ok(()); } - invoke_handle_result(ctx, from_nid, cs_message.payload, sequence_no)?; + invoke_handle_result(ctx, from_nid, cs_message.payload, sequence_no, conn_sn)?; } } Ok(()) @@ -84,6 +87,8 @@ pub fn handle_message<'info>( /// - `ctx`: Context containing all relevant accounts and program-specific information. /// - `from_nid`: Network ID of the source chain that sent the request. /// - `payload`: Encoded payload of the request message. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the request is successfully processed, or an error if @@ -92,6 +97,7 @@ pub fn handle_request( ctx: Context, from_nid: String, payload: &[u8], + conn_sn: u128, ) -> Result<()> { let mut req: CSMessageRequest = payload.try_into()?; @@ -128,7 +134,9 @@ pub fn handle_request( to: req.to().clone(), sn: req.sequence_no(), reqId: req_id, - data: req.data() + data: req.data(), + connection: source.owner.to_owned(), + connSn: conn_sn }); let proxy_request = &mut ctx.accounts.proxy_request; @@ -151,11 +159,13 @@ pub fn handle_request( /// # Arguments /// - `ctx`: The context of accounts involved in the operation. /// - `payload`: The raw result data from the cross-chain operation, which is decoded and processed. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the operation completes successfully, or an error if something /// goes wrong. -pub fn handle_result(ctx: Context, payload: &[u8]) -> Result<()> { +pub fn handle_result(ctx: Context, payload: &[u8], conn_sn: u128) -> Result<()> { let result: CSMessageResult = payload.try_into()?; let proxy_request = &ctx.accounts.proxy_request; let rollback_account = &mut ctx.accounts.rollback_account; @@ -180,7 +190,7 @@ pub fn handle_result(ctx: Context, payload: &[u8]) -> Result<() success_res.success = true; if let Some(message) = &mut result.message() { - handle_reply(ctx, message)?; + handle_reply(ctx, message, conn_sn)?; } else { if proxy_request.is_some() { return Err(XcallError::ProxyRequestAccountMustNotBeSpecified.into()); @@ -250,11 +260,17 @@ pub fn handle_error(ctx: Context, sequence_no: u128) -> Result<( /// # Arguments /// * `ctx` - The context containing relevant accounts for handling the reply. /// * `reply` - The mutable reference to the incoming reply message to be processed. +/// * `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the operation completes successfully, or an error if something /// goes wrong. -pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) -> Result<()> { +pub fn handle_reply( + ctx: Context, + reply: &mut CSMessageRequest, + conn_sn: u128, +) -> Result<()> { let rollback = &ctx.accounts.rollback_account.rollback; if rollback.to().nid() != reply.from().nid() { return Err(XcallError::InvalidReplyReceived.into()); @@ -267,7 +283,9 @@ pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) to: reply.to().clone(), sn: reply.sequence_no(), reqId: req_id, - data: reply.data() + data: reply.data(), + connection: ctx.accounts.connection.owner.to_owned(), + connSn: conn_sn }); let proxy_request = ctx @@ -295,6 +313,8 @@ pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) /// - `ctx`: The context containing the accounts and program-specific info needed for the instruction. /// - `from_nid`: The network ID of the chain that sent the request. /// - `msg_payload`: The payload of the request message received from the source chain. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Indicates whether the invocation was successful or encountered an error. @@ -302,11 +322,13 @@ pub fn invoke_handle_request<'info>( ctx: Context<'_, '_, '_, 'info, HandleMessageCtx<'info>>, from_nid: String, msg_payload: Vec, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_lib::xcall_type::HandleRequestArgs { from_nid, msg_payload, + conn_sn, }; args.serialize(&mut data)?; let ix_data = helper::get_instruction_data(xcall_lib::xcall_type::HANDLE_REQUEST_IX, data); @@ -362,6 +384,8 @@ pub fn invoke_handle_request<'info>( /// - `from_nid`: The network ID of the chain that sent the response. /// - `msg_payload`: The payload of the message received from the destination chain. /// - `sequence_no`: The sequence number associated with the original request message. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Indicates whether the invocation was successful or encountered an error. @@ -370,12 +394,14 @@ pub fn invoke_handle_result<'info>( from_nid: String, msg_payload: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_lib::xcall_type::HandleResultArgs { from_nid, msg_payload, sequence_no, + conn_sn, }; args.serialize(&mut data)?; let ix_data = helper::get_instruction_data(xcall_lib::xcall_type::HANDLE_RESULT_IX, data); @@ -547,7 +573,7 @@ pub struct HandleMessageCtx<'info> { } #[derive(Accounts)] -#[instruction(from_nid: String, msg_payload: Vec)] +#[instruction(from_nid: String, msg_payload: Vec, conn_sn: u128)] pub struct HandleRequestCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -590,7 +616,7 @@ pub struct HandleRequestCtx<'info> { init_if_needed, payer = signer, space = ProxyRequest::SIZE, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &(config.last_req_id + 1).to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.owner.to_bytes()], bump )] pub proxy_request: Account<'info, ProxyRequest>, @@ -609,7 +635,7 @@ pub struct HandleRequestCtx<'info> { } #[derive(Accounts)] -#[instruction(from_nid: String, msg_payload: Vec, sequence_no: u128)] +#[instruction(from_nid: String, msg_payload: Vec, sequence_no: u128, conn_sn: u128)] pub struct HandleResultCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -664,7 +690,7 @@ pub struct HandleResultCtx<'info> { init_if_needed, payer = signer, space = ProxyRequest::SIZE, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &(config.last_req_id + 1).to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.owner.to_bytes()], bump )] pub proxy_request: Option>, diff --git a/contracts/solana/programs/xcall/src/instructions/query_accounts.rs b/contracts/solana/programs/xcall/src/instructions/query_accounts.rs index 23038e50..296e6483 100644 --- a/contracts/solana/programs/xcall/src/instructions/query_accounts.rs +++ b/contracts/solana/programs/xcall/src/instructions/query_accounts.rs @@ -28,15 +28,20 @@ use crate::{ pub fn query_handle_message_accounts( ctx: Context, + from_nid: String, msg: Vec, + conn_sn: u128, ) -> Result { + let connection = &ctx.accounts.connection; let config = &ctx.accounts.config; let admin = config.admin; let (proxy_request, _) = Pubkey::find_program_address( &[ ProxyRequest::SEED_PREFIX.as_bytes(), - &(config.last_req_id + 1).to_be_bytes(), + from_nid.as_bytes(), + &conn_sn.to_be_bytes(), + &connection.owner.to_bytes(), ], &id(), ); @@ -149,7 +154,9 @@ pub fn query_handle_message_accounts( pub fn query_execute_call_accounts( ctx: Context, - req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, page: u8, limit: u8, @@ -158,7 +165,12 @@ pub fn query_execute_call_accounts( let req = &ctx.accounts.proxy_request.req; let (proxy_request, _) = Pubkey::find_program_address( - &[ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + &[ + ProxyRequest::SEED_PREFIX.as_bytes(), + from_nid.as_bytes(), + &conn_sn.to_be_bytes(), + &connection.to_bytes(), + ], &id(), ); @@ -342,7 +354,7 @@ pub fn query_connection_send_message_accoounts<'info>( } #[derive(Accounts)] -#[instruction(req_id: u128, data: Vec)] +#[instruction(req_id: u128, from_nid: String, conn_sn: u128, connection: Pubkey, data: Vec)] pub struct QueryExecuteCallAccountsCtx<'info> { #[account( seeds = [Config::SEED_PREFIX.as_bytes()], @@ -351,7 +363,7 @@ pub struct QueryExecuteCallAccountsCtx<'info> { pub config: Account<'info, Config>, #[account( - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump )] pub proxy_request: Account<'info, ProxyRequest>, @@ -360,6 +372,8 @@ pub struct QueryExecuteCallAccountsCtx<'info> { #[derive(Accounts)] #[instruction(from_nid: String, msg: Vec, sequence_no: u128)] pub struct QueryHandleMessageAccountsCtx<'info> { + pub connection: Signer<'info>, + #[account( seeds = [Config::SEED_PREFIX.as_bytes()], bump = config.bump, diff --git a/contracts/solana/programs/xcall/src/lib.rs b/contracts/solana/programs/xcall/src/lib.rs index 4506dc4e..c4179343 100644 --- a/contracts/solana/programs/xcall/src/lib.rs +++ b/contracts/solana/programs/xcall/src/lib.rs @@ -137,6 +137,8 @@ pub mod xcall { /// - `msg`: The encoded message payload received from the chain. /// - `sequence_no`: The sequence number associated with the message, used to track message /// ordering and responses. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the message is successfully handled, or an error if any @@ -146,8 +148,9 @@ pub mod xcall { from_nid: String, msg: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { - instructions::handle_message(ctx, from_nid, msg, sequence_no) + instructions::handle_message(ctx, from_nid, msg, sequence_no, conn_sn) } /// Instruction: Handle Request @@ -162,16 +165,20 @@ pub mod xcall { /// - `ctx`: Context containing all relevant accounts and program-specific information. /// - `from_nid`: Network ID of the chain that sent the request. /// - `msg_payload`: Encoded payload of the request message. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the request is processed successfully, or an error if /// validation or processing fails. + #[allow(unused_variables)] pub fn handle_request<'info>( ctx: Context<'_, '_, '_, 'info, HandleRequestCtx<'info>>, from_nid: String, msg_payload: Vec, + conn_sn: u128, ) -> Result<()> { - instructions::handle_request(ctx, from_nid, &msg_payload) + instructions::handle_request(ctx, from_nid, &msg_payload, conn_sn) } /// Instruction: Handle Result @@ -187,6 +194,8 @@ pub mod xcall { /// - `from_nid`: Network ID of the chain that sent the result. /// - `msg_payload`: Encoded payload of the result message. /// - `sequence_no`: Unique sequence number of the result message. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the result is processed successfully, or an error if @@ -197,8 +206,9 @@ pub mod xcall { from_nid: String, msg_payload: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { - instructions::handle_result(ctx, &msg_payload) + instructions::handle_result(ctx, &msg_payload, conn_sn) } /// Instruction: Handle Error @@ -357,13 +367,22 @@ pub mod xcall { /// # Parameters /// - `ctx`: The context of the solana program instruction /// - `req_id`: The unique identifier for the request being processed. + /// - `from_nid`: Network ID of the chain that sent the request. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters + /// - `connection`: The connection key used to derive proxy request account with the combination + /// of other parameters /// - `data`: The data associated with the call request, which will be verified and processed. /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the call was executed successfully, or an error if it failed. + #[allow(unused_variables)] pub fn execute_call<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteCallCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, ) -> Result<()> { instructions::execute_call(ctx, req_id, data) @@ -402,6 +421,11 @@ pub mod xcall { /// # Arguments /// * `ctx` - Context containing the accounts required for processing the forced rollback. /// * `req_id` - The unique request ID associated with the message being rolled back. + /// - `from_nid`: Network ID of the chain that sent the request. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters + /// - `connection`: The connection key used to derive proxy request account with the combination + /// of other parameters /// /// # Returns /// * `Result<()>` - Returns `Ok(())` on successful execution, or an error if the rollback process @@ -410,18 +434,27 @@ pub mod xcall { pub fn handle_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, HandleForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { instructions::handle_forced_rollback(ctx) } + #[allow(unused_variables)] pub fn query_execute_call_accounts<'info>( ctx: Context<'_, '_, '_, 'info, QueryExecuteCallAccountsCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, page: u8, limit: u8, ) -> Result { - instructions::query_execute_call_accounts(ctx, req_id, data, page, limit) + instructions::query_execute_call_accounts( + ctx, from_nid, conn_sn, connection, data, page, limit, + ) } #[allow(unused_variables)] @@ -440,8 +473,9 @@ pub mod xcall { from_nid: String, msg: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result { - instructions::query_handle_message_accounts(ctx, msg) + instructions::query_handle_message_accounts(ctx, from_nid, msg, conn_sn) } pub fn query_handle_error_accounts( diff --git a/contracts/solana/tests/centralized-connection/centralized-connection.ts b/contracts/solana/tests/centralized-connection/centralized-connection.ts index 7d3d51f2..8b873a64 100644 --- a/contracts/solana/tests/centralized-connection/centralized-connection.ts +++ b/contracts/solana/tests/centralized-connection/centralized-connection.ts @@ -182,6 +182,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + fromNetwork, connSn, nextSequenceNo, cs_message, @@ -212,7 +213,11 @@ describe("CentralizedConnection", () => { expect(await ctx.getReceipt(fromNetwork, nextSequenceNo)).to.be.empty; // expect proxy request in xcall PDA's account - let proxyRequest = await xcallCtx.getProxyRequest(nextReqId); + let proxyRequest = await xcallCtx.getProxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ); expect(proxyRequest.req.protocols).to.includes( connectionProgram.programId.toString() ); @@ -229,17 +234,30 @@ describe("CentralizedConnection", () => { // call xcall execute_call let executeCallAccounts = await xcallCtx.getExecuteCallAccounts( nextReqId, + fromNetwork, + connSn, + connectionProgram.programId, data ); await xcallProgram.methods - .executeCall(new anchor.BN(nextReqId), Buffer.from(data)) + .executeCall( + new anchor.BN(nextReqId), + fromNetwork, + new anchor.BN(connSn), + connectionProgram.programId, + Buffer.from(data) + ) .accounts({ signer: ctx.admin.publicKey, systemProgram: SYSTEM_PROGRAM_ID, config: XcallPDA.config().pda, admin: xcallConfig.admin, - proxyRequest: XcallPDA.proxyRequest(nextReqId).pda, + proxyRequest: XcallPDA.proxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ).pda, }) .remainingAccounts([...executeCallAccounts.slice(4)]) .signers([ctx.admin]) @@ -348,6 +366,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + ctx.dstNetworkId, connSn, nextSequenceNo, csMessage, @@ -467,6 +486,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + ctx.dstNetworkId, connSn, nextSequenceNo, csMessage, @@ -664,6 +684,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + fromNetwork, connSn, nextSequenceNo, cs_message, @@ -691,7 +712,12 @@ describe("CentralizedConnection", () => { await sleep(2); let executeForcedRollbackIx = await mockDappProgram.methods - .executeForcedRollback(new anchor.BN(nextReqId)) + .executeForcedRollback( + new anchor.BN(nextReqId), + fromNetwork, + new anchor.BN(connSn), + connectionProgram.programId + ) .accountsStrict({ config: DappPDA.config().pda, systemProgram: SYSTEM_PROGRAM_ID, @@ -710,7 +736,11 @@ describe("CentralizedConnection", () => { isWritable: true, }, { - pubkey: XcallPDA.proxyRequest(nextReqId).pda, + pubkey: XcallPDA.proxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ).pda, isSigner: false, isWritable: true, }, diff --git a/contracts/solana/tests/centralized-connection/setup.ts b/contracts/solana/tests/centralized-connection/setup.ts index de6eacb2..02e7dee6 100644 --- a/contracts/solana/tests/centralized-connection/setup.ts +++ b/contracts/solana/tests/centralized-connection/setup.ts @@ -77,6 +77,7 @@ export class TestContext { } async getRecvMessageAccounts( + fromNetwork: string, connSn: number, sequenceNo: number, csMessage: Uint8Array, @@ -106,7 +107,7 @@ export class TestContext { let res = await connectionProgram.methods .queryRecvMessageAccounts( - this.dstNetworkId, + fromNetwork, new anchor.BN(connSn), Buffer.from(csMessage), new anchor.BN(sequenceNo), diff --git a/contracts/solana/tests/xcall/setup.ts b/contracts/solana/tests/xcall/setup.ts index 7cf19fee..f826cd43 100644 --- a/contracts/solana/tests/xcall/setup.ts +++ b/contracts/solana/tests/xcall/setup.ts @@ -82,12 +82,27 @@ export class TestContext { await sleep(2); } - async getExecuteCallAccounts(reqId: number, data: Uint8Array) { + async getExecuteCallAccounts( + reqId: number, + fromNetwork: string, + connSn: number, + connection: PublicKey, + data: Uint8Array + ) { const res = await xcallProgram.methods - .queryExecuteCallAccounts(new anchor.BN(reqId), Buffer.from(data), 1, 30) + .queryExecuteCallAccounts( + new anchor.BN(reqId), + fromNetwork, + new anchor.BN(connSn), + connection, + Buffer.from(data), + 1, + 30 + ) .accountsStrict({ config: XcallPDA.config().pda, - proxyRequest: XcallPDA.proxyRequest(reqId).pda, + proxyRequest: XcallPDA.proxyRequest(fromNetwork, connSn, connection) + .pda, }) .remainingAccounts([ { @@ -145,9 +160,13 @@ export class TestContext { return await xcallProgram.account.config.fetch(pda); } - async getProxyRequest(requestId: number) { + async getProxyRequest( + fromNetwork: string, + connSn: number, + connection: PublicKey + ) { return await xcallProgram.account.proxyRequest.fetch( - XcallPDA.proxyRequest(requestId).pda, + XcallPDA.proxyRequest(fromNetwork, connSn, connection).pda, "confirmed" ); } @@ -192,9 +211,18 @@ export class XcallPDA { return { bump, pda }; } - static proxyRequest(requestId: number) { + static proxyRequest( + fromNetwork: string, + connSn: number, + connection: PublicKey + ) { const [pda, bump] = PublicKey.findProgramAddressSync( - [Buffer.from("proxy"), uint128ToArray(requestId)], + [ + Buffer.from("proxy"), + Buffer.from(fromNetwork), + uint128ToArray(connSn), + connection.toBuffer(), + ], xcallProgram.programId ); From 7cc4e80bd6f2c196841a324d01e5fb5284e24e6d Mon Sep 17 00:00:00 2001 From: bishalbikram Date: Fri, 18 Oct 2024 15:18:13 +0545 Subject: [PATCH 42/75] fix: close redundant proxy request account --- .../solana/programs/xcall/src/instructions/handle_message.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contracts/solana/programs/xcall/src/instructions/handle_message.rs b/contracts/solana/programs/xcall/src/instructions/handle_message.rs index 7450194b..ef3e6617 100644 --- a/contracts/solana/programs/xcall/src/instructions/handle_message.rs +++ b/contracts/solana/programs/xcall/src/instructions/handle_message.rs @@ -122,6 +122,11 @@ pub fn handle_request( pending_request.sources.push(source.owner.to_owned()) } if pending_request.sources.len() != req.protocols().len() { + // close the proxy request as it's no longer needed + ctx.accounts + .proxy_request + .close(ctx.accounts.signer.to_account_info())?; + return Ok(()); } pending_request.close(ctx.accounts.admin.clone())?; From 3c034a26966bea6cf80efff2f86f5a1c8146a280 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Mon, 21 Oct 2024 13:17:25 +0545 Subject: [PATCH 43/75] fix: stellar cluster connection --- Cargo.lock | 136 ++------- contracts/soroban/Cargo.lock | 267 +++--------------- .../cluster-connection/src/contract.rs | 21 +- .../cluster-connection/src/helpers.rs | 7 + .../cluster-connection/src/storage.rs | 11 + .../contracts/cluster-connection/src/test.rs | 2 + .../contracts/cluster-connection/src/types.rs | 2 + 7 files changed, 89 insertions(+), 357 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f00ebfe0..b673ca4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -151,7 +151,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -160,7 +160,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -285,21 +285,10 @@ dependencies = [ "winapi", ] -[[package]] -name = "clear_on_drop" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38508a63f4979f0048febc9966fadbd48e5dab31fd0ec6a3f151bbf4a74f7423" -dependencies = [ - "cc", -] - [[package]] name = "cluster-connection" version = "0.0.0" dependencies = [ - "curve25519-dalek 1.2.6", - "ed25519-dalek 1.0.1", "soroban-sdk", ] @@ -473,7 +462,7 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", "subtle", "zeroize", @@ -485,7 +474,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array 0.14.7", + "generic-array", "rand_core 0.6.4", "subtle", "zeroize", @@ -497,7 +486,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array", "typenum", ] @@ -511,19 +500,6 @@ dependencies = [ "syn 2.0.42", ] -[[package]] -name = "curve25519-dalek" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d59fed08e452f286b251f88b2fc64a01f50a7b263aa09557ad7285d9e7fa" -dependencies = [ - "byteorder", - "clear_on_drop", - "digest 0.8.1", - "rand_core 0.3.1", - "subtle", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1055,22 +1031,13 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - [[package]] name = "digest" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -1172,19 +1139,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519 1.5.3", - "rand 0.7.3", - "sha2 0.9.9", - "zeroize", -] - [[package]] name = "ed25519-dalek" version = "2.1.1" @@ -1232,7 +1186,7 @@ dependencies = [ "der 0.6.1", "digest 0.10.7", "ff 0.12.1", - "generic-array 0.14.7", + "generic-array", "group 0.12.1", "pkcs8 0.9.0", "rand_core 0.6.4", @@ -1251,7 +1205,7 @@ dependencies = [ "crypto-bigint 0.5.5", "digest 0.10.7", "ff 0.13.0", - "generic-array 0.14.7", + "generic-array", "group 0.13.0", "pkcs8 0.10.2", "rand_core 0.6.4", @@ -1440,15 +1394,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2243,17 +2188,6 @@ dependencies = [ "proc-macro2 1.0.70", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -2261,20 +2195,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -2285,21 +2209,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -2315,15 +2224,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "redox_syscall" version = "0.3.5" @@ -2562,7 +2462,7 @@ checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct 0.1.1", "der 0.6.1", - "generic-array 0.14.7", + "generic-array", "pkcs8 0.9.0", "subtle", "zeroize", @@ -2576,7 +2476,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", "der 0.7.9", - "generic-array 0.14.7", + "generic-array", "pkcs8 0.10.2", "subtle", "zeroize", @@ -2840,9 +2740,9 @@ dependencies = [ "backtrace", "curve25519-dalek 4.1.3", "ecdsa 0.16.9", - "ed25519-dalek 2.1.1", + "ed25519-dalek", "elliptic-curve 0.13.8", - "generic-array 0.14.7", + "generic-array", "getrandom", "hex-literal 0.4.1", "hmac", @@ -2851,8 +2751,8 @@ dependencies = [ "num-integer", "num-traits", "p256", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "sec1 0.7.3", "sha2 0.10.8", "sha3", @@ -2909,8 +2809,8 @@ dependencies = [ "arbitrary", "bytes-lit", "ctor", - "ed25519-dalek 2.1.1", - "rand 0.8.5", + "ed25519-dalek", + "rand", "serde", "serde_json", "soroban-env-guest", @@ -3208,7 +3108,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test 0.16.4", "hex", - "hex-literal 0.4.1", + "hex-literal 0.3.4", "ibc-proto", "prost 0.11.9", "serde", diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index b02fd6ef..b8c8a485 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -92,22 +92,13 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array", ] [[package]] @@ -116,12 +107,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes-lit" version = "0.0.5" @@ -166,21 +151,10 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "clear_on_drop" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38508a63f4979f0048febc9966fadbd48e5dab31fd0ec6a3f151bbf4a74f7423" -dependencies = [ - "cc", -] - [[package]] name = "cluster-connection" version = "0.0.0" dependencies = [ - "curve25519-dalek 1.2.6", - "ed25519-dalek 1.0.1", "soroban-sdk", ] @@ -222,8 +196,8 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array 0.14.7", - "rand_core 0.6.4", + "generic-array", + "rand_core", "subtle", "zeroize", ] @@ -234,7 +208,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array", "typenum", ] @@ -248,32 +222,6 @@ dependencies = [ "syn", ] -[[package]] -name = "curve25519-dalek" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d59fed08e452f286b251f88b2fc64a01f50a7b263aa09557ad7285d9e7fa" -dependencies = [ - "byteorder", - "clear_on_drop", - "digest 0.8.1", - "rand_core 0.3.1", - "subtle", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - [[package]] name = "curve25519-dalek" version = "4.1.2" @@ -283,7 +231,7 @@ dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", "platforms", "rustc_version", @@ -368,31 +316,13 @@ dependencies = [ "syn", ] -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.7", -] - [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.4", + "block-buffer", "const-oid", "crypto-common", "subtle", @@ -411,22 +341,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", - "signature 2.2.0", + "signature", "spki", ] -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - [[package]] name = "ed25519" version = "2.2.3" @@ -434,20 +355,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.2.0", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519 1.5.3", - "rand 0.7.3", - "sha2 0.9.9", - "zeroize", + "signature", ] [[package]] @@ -456,11 +364,11 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ - "curve25519-dalek 4.1.2", - "ed25519 2.2.3", - "rand_core 0.6.4", + "curve25519-dalek", + "ed25519", + "rand_core", "serde", - "sha2 0.10.8", + "sha2", "zeroize", ] @@ -478,12 +386,12 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", - "generic-array 0.14.7", + "generic-array", "group", "pkcs8", - "rand_core 0.6.4", + "rand_core", "sec1", "subtle", "zeroize", @@ -513,7 +421,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -529,15 +437,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -575,7 +474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] @@ -612,7 +511,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", + "digest", ] [[package]] @@ -706,8 +605,8 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.8", - "signature 2.2.0", + "sha2", + "signature", ] [[package]] @@ -824,12 +723,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "paste" version = "1.0.14" @@ -892,17 +785,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - [[package]] name = "rand" version = "0.8.5" @@ -910,18 +792,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_chacha", + "rand_core", ] [[package]] @@ -931,30 +803,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -964,15 +815,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -1012,7 +854,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array 0.14.7", + "generic-array", "pkcs8", "subtle", "zeroize", @@ -1085,19 +927,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "sha2" version = "0.10.8" @@ -1106,7 +935,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.7", + "digest", ] [[package]] @@ -1115,24 +944,18 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", + "digest", + "rand_core", ] [[package]] @@ -1188,8 +1011,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" dependencies = [ "backtrace", - "curve25519-dalek 4.1.2", - "ed25519-dalek 2.0.0", + "curve25519-dalek", + "ed25519-dalek", "getrandom", "hex-literal", "hmac", @@ -1197,9 +1020,9 @@ dependencies = [ "num-derive", "num-integer", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", - "sha2 0.10.8", + "rand", + "rand_chacha", + "sha2", "sha3", "soroban-builtin-sdk-macros", "soroban-env-common", @@ -1253,8 +1076,8 @@ dependencies = [ "arbitrary", "bytes-lit", "ctor", - "ed25519-dalek 2.0.0", - "rand 0.8.5", + "ed25519-dalek", + "rand", "serde", "serde_json", "soroban-env-guest", @@ -1276,7 +1099,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "sha2 0.10.8", + "sha2", "soroban-env-common", "soroban-spec", "soroban-spec-rust", @@ -1305,7 +1128,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "sha2 0.10.8", + "sha2", "soroban-spec", "stellar-xdr", "syn", @@ -1656,17 +1479,3 @@ name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/contracts/soroban/contracts/cluster-connection/src/contract.rs b/contracts/soroban/contracts/cluster-connection/src/contract.rs index c9502f03..a8d329f6 100644 --- a/contracts/soroban/contracts/cluster-connection/src/contract.rs +++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs @@ -12,7 +12,8 @@ impl ClusterConnection { storage::store_native_token(&env, msg.native_token); storage::store_conn_sn(&env, 0); - storage::store_admin(&env, msg.relayer); + 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); @@ -27,7 +28,7 @@ impl ClusterConnection { } pub fn set_admin(env: Env, address: Address) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; storage::store_admin(&env, address); Ok(()) } @@ -74,7 +75,7 @@ impl ClusterConnection { conn_sn: u128, msg: Bytes, ) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; if storage::get_sn_receipt(&env, src_network.clone(), conn_sn) { return Err(ContractError::DuplicateMessage); @@ -86,7 +87,7 @@ impl ClusterConnection { } pub fn revert_message(env: &Env, sn: u128) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; helpers::call_xcall_handle_error(&env, sn)?; Ok(()) @@ -99,7 +100,7 @@ impl ClusterConnection { msg: Bytes, signatures: Vec>, ) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; if !helpers::verify_signatures(&env, signatures, msg.clone()){ return Err(ContractError::SignatureVerificationFailed); @@ -120,14 +121,14 @@ impl ClusterConnection { message_fee: u128, response_fee: u128, ) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + 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_admin(&env)?; + let admin = helpers::ensure_relayer(&env)?; let token_addr = storage::native_token(&env)?; let client = token::Client::new(&env, &token_addr); @@ -138,7 +139,7 @@ impl ClusterConnection { } pub fn add_validator(env: Env, address: Address) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; let validators = storage::get_validators(&env).unwrap(); if validators.contains(&address) { return Err(ContractError::ValidatorAlreadyAdded); @@ -153,13 +154,13 @@ impl ClusterConnection { } pub fn set_validators_threshold(env: Env, threshold: u32) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; storage::store_validator_threshold(&env, threshold); Ok(()) } pub fn remove_validator(env: Env, address: Address) -> Result<(), ContractError> { - helpers::ensure_admin(&env)?; + helpers::ensure_relayer(&env)?; let validators: Vec
= storage::get_validators(&env).unwrap(); let threshold = storage::get_validators_threshold(&env).unwrap(); let admin = storage::admin(&env).unwrap(); diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs index 6f88586e..eb823868 100644 --- a/contracts/soroban/contracts/cluster-connection/src/helpers.rs +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -3,6 +3,13 @@ use core::panic; use soroban_sdk::{token, Address, Bytes, BytesN, Env, FromVal, Map, String, Vec}; use crate::{errors::ContractError, interfaces::interface_xcall::XcallClient, storage}; +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(); diff --git a/contracts/soroban/contracts/cluster-connection/src/storage.rs b/contracts/soroban/contracts/cluster-connection/src/storage.rs index 256a44bc..3e98e731 100644 --- a/contracts/soroban/contracts/cluster-connection/src/storage.rs +++ b/contracts/soroban/contracts/cluster-connection/src/storage.rs @@ -29,6 +29,13 @@ pub fn admin(e: &Env) -> Result { .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() @@ -123,6 +130,10 @@ pub fn store_receipt(e: &Env, network_id: String, sn: u128) { 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); } diff --git a/contracts/soroban/contracts/cluster-connection/src/test.rs b/contracts/soroban/contracts/cluster-connection/src/test.rs index 3f7e978a..17470c70 100644 --- a/contracts/soroban/contracts/cluster-connection/src/test.rs +++ b/contracts/soroban/contracts/cluster-connection/src/test.rs @@ -49,6 +49,7 @@ impl TestContext { self.env.mock_all_auths(); client.initialize(&InitializeMsg { + admin: self.relayer.clone(), relayer: self.relayer.clone(), native_token: self.native_token.clone(), xcall_address: self.xcall.clone(), @@ -66,6 +67,7 @@ impl TestContext { 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), diff --git a/contracts/soroban/contracts/cluster-connection/src/types.rs b/contracts/soroban/contracts/cluster-connection/src/types.rs index cffcfc46..bf5f551d 100644 --- a/contracts/soroban/contracts/cluster-connection/src/types.rs +++ b/contracts/soroban/contracts/cluster-connection/src/types.rs @@ -4,6 +4,7 @@ use soroban_sdk::{contracttype, Address, String}; #[derive(Clone)] pub enum StorageKey { Xcall, + Relayer, Admin, UpgradeAuthority, Xlm, @@ -17,6 +18,7 @@ pub enum StorageKey { #[contracttype] pub struct InitializeMsg { pub relayer: Address, + pub admin: Address, pub native_token: Address, pub xcall_address: Address, pub upgrade_authority: Address, From de9a2931d0a772413e2c4b1a4bf95abdc7b66207 Mon Sep 17 00:00:00 2001 From: bishalbikram Date: Thu, 26 Sep 2024 12:27:11 +0545 Subject: [PATCH 44/75] fix: stellar external audit issues fix: remove soroban contracts from root workspace members chore: upgrade soroban-sdk version to 21.7.4 chore: use different error message for different issues chore: add contract version methods and remove unused methods chore: add unit test for execute call and edge cases --- Cargo.toml | 6 +- contracts/soroban/Cargo.lock | 91 ++++--- contracts/soroban/Cargo.toml | 2 +- .../centralized-connection/Cargo.toml | 2 + .../centralized-connection/src/contract.rs | 8 +- .../centralized-connection/src/storage.rs | 20 +- .../centralized-connection/src/test.rs | 147 +++++++++++- .../centralized-connection/src/types.rs | 1 + .../contracts/mock-dapp-multi/src/contract.rs | 7 + .../contracts/mock-dapp-multi/src/storage.rs | 13 + .../mock-dapp-multi/src/test/contract.rs | 13 + .../mock-dapp-multi/src/test/setup.rs | 17 +- .../contracts/mock-dapp-multi/src/types.rs | 1 + .../soroban/contracts/xcall/src/contract.rs | 8 +- .../soroban/contracts/xcall/src/errors.rs | 2 + .../contracts/xcall/src/handle_message.rs | 16 +- .../soroban/contracts/xcall/src/helpers.rs | 7 - .../soroban/contracts/xcall/src/storage.rs | 13 + .../contracts/xcall/src/test/contract.rs | 27 +++ .../contracts/xcall/src/test/execute_call.rs | 223 +++++++++++++++++- .../xcall/src/test/handle_message.rs | 217 ++++++++++++++++- .../contracts/xcall/src/test/send_message.rs | 56 ++++- .../soroban/contracts/xcall/src/test/setup.rs | 31 ++- .../contracts/xcall/src/types/request.rs | 5 - .../xcall/src/types/storage_types.rs | 1 + 25 files changed, 841 insertions(+), 93 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ee82d52..19f8128c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ - "contracts/cosmwasm-vm/*", - "contracts/soroban/contracts/*", - "contracts/soroban/libs/*" + "contracts/cosmwasm-vm/*" ] [workspace.package] @@ -38,8 +36,6 @@ cw-common={ git="https://github.com/icon-project/IBC-Integration.git", branch = cw-mock-dapp = {path="contracts/cosmwasm-vm/cw-mock-dapp"} cw-mock-dapp-multi = { path="contracts/cosmwasm-vm/cw-mock-dapp-multi"} -soroban-sdk = "21.6.0" - [profile.release] opt-level = 'z' debug = false diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index e571a9ff..9ac56a3a 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -130,6 +130,8 @@ name = "centralized-connection" version = "0.0.0" dependencies = [ "soroban-sdk", + "soroban-xcall-lib", + "xcall", ] [[package]] @@ -338,7 +340,6 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", - "spki", ] [[package]] @@ -353,15 +354,16 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", + "subtle", "zeroize", ] @@ -383,7 +385,6 @@ dependencies = [ "ff", "generic-array", "group", - "pkcs8", "rand_core", "sec1", "subtle", @@ -597,9 +598,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", - "signature", ] [[package]] @@ -716,6 +715,18 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -760,6 +771,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -848,7 +868,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -959,9 +978,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "soroban-builtin-sdk-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32c6e817f3ca269764ec0d7d14da6210b74a5bf14d4e745aa3ee860558900" +checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" dependencies = [ "itertools", "proc-macro2", @@ -971,9 +990,9 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14e18d879c520ff82612eaae0590acaf6a7f3b977407e1abb1c9e31f94c7814" +checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" dependencies = [ "arbitrary", "crate-git-revision", @@ -985,13 +1004,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-xdr", + "wasmparser", ] [[package]] name = "soroban-env-guest" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5122ca2abd5ebcc1e876a96b9b44f87ce0a0e06df8f7c09772ddb58b159b7454" +checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" dependencies = [ "soroban-env-common", "static_assertions", @@ -999,13 +1019,16 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" +checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" dependencies = [ "backtrace", "curve25519-dalek", + "ecdsa", "ed25519-dalek", + "elliptic-curve", + "generic-array", "getrandom", "hex-literal", "hmac", @@ -1013,8 +1036,10 @@ dependencies = [ "num-derive", "num-integer", "num-traits", + "p256", "rand", "rand_chacha", + "sec1", "sha2", "sha3", "soroban-builtin-sdk-macros", @@ -1022,13 +1047,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-strkey", + "wasmparser", ] [[package]] name = "soroban-env-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13e3f8c86f812e0669e78fcb3eae40c385c6a9dd1a4886a1de733230b4fcf27" +checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" dependencies = [ "itertools", "proc-macro2", @@ -1041,9 +1067,9 @@ dependencies = [ [[package]] name = "soroban-ledger-snapshot" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a54708f44890e0546180db6b4f530e2a88d83b05a9b38a131caa21d005e25a" +checksum = "956476365ff3f9bf429ff23fa11ac75798347a2bfc3c9e5e12638dbe3a6b17a8" dependencies = [ "serde", "serde_json", @@ -1062,9 +1088,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fc8be9068dd4e0212d8b13ad61089ea87e69ac212c262914503a961c8dc3a3" +checksum = "c7767472f00a4053e86d5c37b3c814a6bc01c9230004713328d73d2a3444e72e" dependencies = [ "arbitrary", "bytes-lit", @@ -1082,9 +1108,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db20def4ead836663633f58d817d0ed8e1af052c9650a04adf730525af85b964" +checksum = "be8cf8fa10f3ad62509ff7b25cd696fb837da692c40264d1abb393e117aad75c" dependencies = [ "crate-git-revision", "darling", @@ -1102,9 +1128,9 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eefeb5d373b43f6828145d00f0c5cc35e96db56a6671ae9614f84beb2711cab" +checksum = "12d306f61ef5c1247dca1562e04cc74b6e3adf107631c168b2ce0d5f1cf1fa13" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1114,9 +1140,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3152bca4737ef734ac37fe47b225ee58765c9095970c481a18516a2b287c7a33" +checksum = "bed06e0f622fb878fc439643f2fd86163223ac33a468beeea96e5d33f79b08b3" dependencies = [ "prettyplease", "proc-macro2", @@ -1183,9 +1209,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "20.1.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59cdf3eb4467fb5a4b00b52e7de6dca72f67fac6f9b700f55c95a5d86f09c9d" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1369,11 +1395,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.88.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.2.6", + "semver", ] [[package]] diff --git a/contracts/soroban/Cargo.toml b/contracts/soroban/Cargo.toml index cddad916..7cb79f87 100644 --- a/contracts/soroban/Cargo.toml +++ b/contracts/soroban/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.dependencies] -soroban-sdk = "20.5.0" +soroban-sdk = "21.7.4" [profile.release] opt-level = "z" diff --git a/contracts/soroban/contracts/centralized-connection/Cargo.toml b/contracts/soroban/contracts/centralized-connection/Cargo.toml index c4f294fa..e34ece52 100644 --- a/contracts/soroban/contracts/centralized-connection/Cargo.toml +++ b/contracts/soroban/contracts/centralized-connection/Cargo.toml @@ -10,6 +10,8 @@ doctest = false [dependencies] soroban-sdk = { workspace = true, features = ["alloc"] } +xcall = { path = "../xcall" } +soroban-xcall-lib = { path = "../../libs/soroban-xcall-lib" } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/soroban/contracts/centralized-connection/src/contract.rs b/contracts/soroban/contracts/centralized-connection/src/contract.rs index 507f5451..03444560 100644 --- a/contracts/soroban/contracts/centralized-connection/src/contract.rs +++ b/contracts/soroban/contracts/centralized-connection/src/contract.rs @@ -125,11 +125,13 @@ impl CentralizedConnection { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/centralized-connection/src/storage.rs b/contracts/soroban/contracts/centralized-connection/src/storage.rs index 4a410aa0..a4a2fa24 100644 --- a/contracts/soroban/contracts/centralized-connection/src/storage.rs +++ b/contracts/soroban/contracts/centralized-connection/src/storage.rs @@ -50,13 +50,6 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } -pub fn get_conn_sn(e: &Env) -> Result { - e.storage() - .instance() - .get(&StorageKey::ConnSn) - .ok_or(ContractError::Uninitialized) -} - pub fn get_next_conn_sn(e: &Env) -> u128 { let mut sn = e.storage().instance().get(&StorageKey::ConnSn).unwrap_or(0); sn += 1; @@ -103,6 +96,19 @@ pub fn get_sn_receipt(e: &Env, network_id: String, sn: u128) -> bool { is_received } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_receipt(e: &Env, network_id: String, sn: u128) { let key = StorageKey::Receipts(network_id, sn); e.storage().persistent().set(&key, &true); diff --git a/contracts/soroban/contracts/centralized-connection/src/test.rs b/contracts/soroban/contracts/centralized-connection/src/test.rs index a44bd217..7556c384 100644 --- a/contracts/soroban/contracts/centralized-connection/src/test.rs +++ b/contracts/soroban/contracts/centralized-connection/src/test.rs @@ -2,9 +2,14 @@ extern crate std; -mod xcall { +mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +mod connection { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" + ); +} use crate::{ contract::{CentralizedConnection, CentralizedConnectionClient}, @@ -13,9 +18,14 @@ use crate::{ types::InitializeMsg, }; use soroban_sdk::{ - symbol_short, + bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, - token, vec, Address, Bytes, Env, IntoVal, String, Symbol, + token, vec, Address, Bytes, Env, IntoVal, String, Symbol, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; +use xcall::{ + storage as xcall_storage, + types::{message::CSMessage, request::CSMessageRequest, rollback::Rollback}, }; pub struct TestContext { @@ -33,11 +43,12 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { - xcall: env.register_contract_wasm(None, xcall::WASM), + xcall: env.register_contract_wasm(None, xcall_module::WASM), contract: env.register_contract(None, CentralizedConnection), relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "icon"), upgrade_authority: Address::generate(&env), env, @@ -54,6 +65,23 @@ impl TestContext { xcall_address: self.xcall.clone(), upgrade_authority: self.upgrade_authority.clone(), }); + + self.init_xcall_state(); + } + + pub fn init_xcall_state(&self) { + let xcall_client = xcall_module::Client::new(&self.env, &self.xcall); + + let initialize_msg = xcall_module::InitializeMsg { + native_token: self.native_token.clone(), + network_id: self.nid.clone(), + sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), + }; + xcall_client.initialize(&initialize_msg); + + xcall_client.set_protocol_fee(&100_u128); + xcall_client.set_default_connection(&self.nid, &self.contract) } pub fn init_send_message(&self, client: &CentralizedConnectionClient<'static>) { @@ -65,9 +93,11 @@ impl TestContext { } fn get_dummy_initialize_msg(env: &Env) -> InitializeMsg { + let native_token_contract = env.register_stellar_asset_contract_v2(Address::generate(&env)); + InitializeMsg { relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(Address::generate(&env)), + native_token: native_token_contract.address(), xcall_address: Address::generate(&env), upgrade_authority: Address::generate(&env), } @@ -343,3 +373,108 @@ fn test_get_receipt_returns_false() { let receipt = client.get_receipt(&ctx.nid, &sequence_no); assert_eq!(receipt, true) } + +#[test] +fn test_recv_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #5)")] +fn test_recv_message_duplicate_connection_sequence() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); + + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +pub fn test_revert_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let to = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let rollback = Rollback::new( + Address::generate(&ctx.env), + to, + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.xcall, || { + xcall_storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.revert_message(&sequence_no); + + ctx.env.as_contract(&ctx.xcall, || { + // rollback should be enabled + let rollback = xcall_storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); +} + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(connection::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/centralized-connection/src/types.rs b/contracts/soroban/contracts/centralized-connection/src/types.rs index e3f08bdb..6b8c2fab 100644 --- a/contracts/soroban/contracts/centralized-connection/src/types.rs +++ b/contracts/soroban/contracts/centralized-connection/src/types.rs @@ -8,6 +8,7 @@ pub enum StorageKey { UpgradeAuthority, Xlm, ConnSn, + Version, NetworkFee(String), Receipts(String, u128), } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs index dc3c8573..cd47ec6a 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs @@ -125,9 +125,16 @@ impl MockDapp { helpers::ensure_admin(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) + } + fn process_message( message_type: u8, data: Bytes, diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs index 2a866ea2..e7f2533f 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs @@ -26,6 +26,19 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, admin: Address) { e.storage().instance().set(&StorageKey::Admin, &admin); } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs index 09977786..037f1876 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs @@ -213,3 +213,16 @@ fn test_handle_call_message_reply() { &Some(vec![&ctx.env]), ); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = MockDappClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(mock_dapp::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs index ccaa3877..17e173ec 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs @@ -3,16 +3,22 @@ use soroban_xcall_lib::network_address::NetworkAddress; use crate::contract::{MockDapp, MockDappClient}; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } -mod xcall_module { +pub mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +pub mod mock_dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + pub fn get_dummy_network_address(env: &Env) -> NetworkAddress { let network_id = String::from_str(&env, "stellar"); let account = String::from_str( @@ -31,6 +37,7 @@ pub struct TestContext { pub env: Env, pub native_token: Address, pub xcall: Address, + pub upgrade_authority: Address, pub centralized_connection: Address, } @@ -38,14 +45,16 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let address = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(address); Self { contract: env.register_contract(None, MockDapp), nid: String::from_str(&env, "stellar"), admin: Address::generate(&env), - native_token: env.register_stellar_asset_contract(address), + native_token: native_token_contract.address(), network_address: get_dummy_network_address(&env), xcall: env.register_contract_wasm(None, xcall_module::WASM), + upgrade_authority: Address::generate(&env), centralized_connection: env.register_contract_wasm(None, connection::WASM), env, } @@ -72,6 +81,7 @@ impl TestContext { native_token: self.native_token.clone(), network_id: self.nid.clone(), sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), }; xcall_client.initialize(&initialize_msg); @@ -86,6 +96,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: Address::generate(&self.env), xcall_address: self.xcall.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs index 88ab759e..8b23c376 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs @@ -6,6 +6,7 @@ pub enum StorageKey { Admin, Xlm, Sn, + Version, Rollback(u128), Connections(String), } diff --git a/contracts/soroban/contracts/xcall/src/contract.rs b/contracts/soroban/contracts/xcall/src/contract.rs index 47c2eec9..8d81f6ac 100644 --- a/contracts/soroban/contracts/xcall/src/contract.rs +++ b/contracts/soroban/contracts/xcall/src/contract.rs @@ -153,11 +153,13 @@ impl Xcall { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/xcall/src/errors.rs b/contracts/soroban/contracts/xcall/src/errors.rs index e9896817..ad05c5c3 100644 --- a/contracts/soroban/contracts/xcall/src/errors.rs +++ b/contracts/soroban/contracts/xcall/src/errors.rs @@ -21,4 +21,6 @@ pub enum ContractError { InvalidReplyReceived = 15, InvalidRlpLength = 16, NoRollbackData = 17, + NetworkIdMismatch = 18, + InvalidSourceNetwork = 19, } diff --git a/contracts/soroban/contracts/xcall/src/handle_message.rs b/contracts/soroban/contracts/xcall/src/handle_message.rs index 56831a99..e76bcfed 100644 --- a/contracts/soroban/contracts/xcall/src/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/handle_message.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Bytes, Env, String, Vec}; +use soroban_sdk::{Address, Bytes, BytesN, Env, String, Vec}; use crate::{ errors::ContractError, @@ -21,7 +21,7 @@ pub fn handle_message( let config = storage::get_config(&env)?; if config.network_id == from_nid { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::InvalidSourceNetwork); } let cs_message: CSMessage = CSMessage::decode(&env, msg)?; @@ -43,7 +43,7 @@ pub fn handle_request( let (src_net, _) = req.from().parse_network_address(&env); if src_net != from_net { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::NetworkIdMismatch); } let source = sender.to_string(); let source_valid = is_valid_source(&env, &source, src_net, &req.protocols())?; @@ -52,7 +52,7 @@ pub fn handle_request( } if req.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_request = storage::get_pending_request(&env, hash.clone()); if !pending_request.contains(source.clone()) { @@ -96,7 +96,7 @@ pub fn handle_result(env: &Env, sender: &Address, data: Bytes) -> Result<(), Con } if rollback.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_response = storage::get_pending_response(&env, hash.clone()); if !pending_response.contains(source.clone()) { @@ -179,8 +179,10 @@ pub fn is_valid_source( return Ok(true); } if protocols.len() == 0 { - let default_connection = storage::default_connection(e, src_net)?; - return Ok(sender.clone() == default_connection.to_string()); + let default_connection = storage::default_connection(e, src_net); + if default_connection.is_ok() { + return Ok(sender.clone() == default_connection.unwrap().to_string()); + } } Ok(false) } diff --git a/contracts/soroban/contracts/xcall/src/helpers.rs b/contracts/soroban/contracts/xcall/src/helpers.rs index 5c6ff75d..6bd74db4 100644 --- a/contracts/soroban/contracts/xcall/src/helpers.rs +++ b/contracts/soroban/contracts/xcall/src/helpers.rs @@ -24,13 +24,6 @@ pub fn ensure_upgrade_authority(e: &Env) -> Result { Ok(authority) } -pub fn ensure_fee_handler(e: &Env) -> Result { - let fee_handler = storage::get_fee_handler(&e)?; - fee_handler.require_auth(); - - Ok(fee_handler) -} - pub fn ensure_data_size(len: usize) -> Result<(), ContractError> { if len > MAX_DATA_SIZE as usize { return Err(ContractError::MaxDataSizeExceeded); diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs index 5deef0c5..cfa43a98 100644 --- a/contracts/soroban/contracts/xcall/src/storage.rs +++ b/contracts/soroban/contracts/xcall/src/storage.rs @@ -153,6 +153,19 @@ pub fn get_own_network_address(e: &Env) -> Result Ok(from) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, address: &Address) { e.storage().instance().set(&StorageKey::Admin, &address); extend_instance(&e); diff --git a/contracts/soroban/contracts/xcall/src/test/contract.rs b/contracts/soroban/contracts/xcall/src/test/contract.rs index e7a4d06f..5ad3267a 100644 --- a/contracts/soroban/contracts/xcall/src/test/contract.rs +++ b/contracts/soroban/contracts/xcall/src/test/contract.rs @@ -156,6 +156,20 @@ fn test_get_fee() { assert_eq!(fee, protocol_fee + centralized_conn_fee) } +#[test] +fn test_get_network_address() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let network_address = client.get_network_address(); + let expected_network_address = String::from_str( + &ctx.env, + "icon/CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + ); + assert_eq!(network_address, expected_network_address); +} + #[test] fn test_set_upgrade_authority() { let ctx = TestContext::default(); @@ -183,3 +197,16 @@ fn test_set_upgrade_authority() { let autorhity = client.get_upgrade_authority(); assert_eq!(autorhity, new_upgrade_authority); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(xcall::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/xcall/src/test/execute_call.rs b/contracts/soroban/contracts/xcall/src/test/execute_call.rs index 108ef360..69ca24c7 100644 --- a/contracts/soroban/contracts/xcall/src/test/execute_call.rs +++ b/contracts/soroban/contracts/xcall/src/test/execute_call.rs @@ -1,10 +1,188 @@ #![cfg(test)] -use crate::{contract::XcallClient, storage, types::rollback::Rollback}; -use soroban_sdk::{bytes, testutils::Address as _, Address}; +use soroban_rlp::encoder; +use soroban_sdk::{ + bytes, + testutils::{Address as _, Events}, + vec, Address, Bytes, IntoVal, String, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; + +use crate::{ + contract::XcallClient, + event::{CallExecutedEvent, RollbackExecutedEvent}, + storage, + types::{request::CSMessageRequest, rollback::Rollback}, +}; use super::setup::*; +#[test] +fn test_execute_call_with_persistent_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let sequence_no = 1; + let from = NetworkAddress::new(&ctx.env, ctx.nid, ctx.contract.to_string()); + let mut req = CSMessageRequest::new( + from, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessagePersisted, + Bytes::new(&ctx.env), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_call_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "rollback")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 0, + msg: String::from_str(&ctx.env, "unknown error"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_rollback_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "abc")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + Vec::new(&ctx.env), + MessageType::CallMessageWithRollback, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().get(1).unwrap()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #11)")] +fn test_execute_call_data_mismatch() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let req = get_dummy_message_request(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_execute_rollback_fail_for_invalid_sequence_number() { @@ -36,3 +214,44 @@ fn test_execute_rollback_fail_not_enabled() { client.execute_rollback(&sequence_no); } + +#[test] +fn test_execute_rollback_success() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = Rollback::new( + ctx.dapp, + ctx.network_address, + get_dummy_sources(&ctx.env), + Bytes::new(&ctx.env), + true, + ); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.execute_rollback(&sequence_no); + + let rollback_executed_event = RollbackExecutedEvent { sn: sequence_no }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("RollbackExecuted",).into_val(&ctx.env), + rollback_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be removed + assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); + }); +} diff --git a/contracts/soroban/contracts/xcall/src/test/handle_message.rs b/contracts/soroban/contracts/xcall/src/test/handle_message.rs index 286213a1..5788e7d9 100644 --- a/contracts/soroban/contracts/xcall/src/test/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/handle_message.rs @@ -1,11 +1,13 @@ #![cfg(test)] +extern crate std; + use soroban_sdk::{ bytes, - testutils::{Address as _, Events}, - vec, Address, IntoVal, String, + testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, + vec, Address, Bytes, BytesN, IntoVal, String, Symbol, }; -use soroban_xcall_lib::messages::msg_type::MessageType; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; use crate::{ contract::XcallClient, @@ -15,13 +17,14 @@ use crate::{ message::CSMessage, request::CSMessageRequest, result::{CSMessageResult, CSResponseType}, + rollback::Rollback, }, }; use super::setup::*; #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #19)")] fn test_handle_message_fail_for_same_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -35,7 +38,7 @@ fn test_handle_message_fail_for_same_network_id() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #18)")] fn test_handle_message_request_fail_for_invalid_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -82,6 +85,35 @@ fn test_handle_message_request_fail_for_invalid_source() { ); } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #9)")] +fn test_handle_message_request_fail_for_invalid_source_2() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + vec![&ctx.env], + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request).encode(&ctx.env); + + client.handle_message( + &Address::generate(&ctx.env), + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_request_from_default_connection() { let ctx = TestContext::default(); @@ -142,7 +174,7 @@ fn test_handle_message_request_from_multiple_sources() { assert_eq!(res, ()); let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); - let hash = ctx.env.crypto().keccak256(cs_message.payload()); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); ctx.env.as_contract(&ctx.contract, || { let pending_requests = storage::get_pending_request(&ctx.env, hash); @@ -177,6 +209,56 @@ fn test_handle_message_request_from_multiple_sources() { ) } +#[test] +fn test_handle_message_result_from_multiple_protocols() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols = get_dummy_protocols(&ctx.env); + + let sequence_no = 1; + let rollback = Rollback::new( + Address::generate(&ctx.env), + get_dummy_network_address(&ctx.env), + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + Bytes::new(&ctx.env), + ); + let encoded = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + for (i, protocol) in protocols.iter().enumerate() { + let from_nid = String::from_str(&ctx.env, "s"); + let sender = Address::from_string(&protocol); + + let res = client.handle_message(&sender, &from_nid, &encoded); + assert_eq!(res, ()); + + let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); + + ctx.env.as_contract(&ctx.contract, || { + let pending_responses = storage::get_pending_response(&ctx.env, hash); + + let i = i as u32 + 1; + if i < protocols.len() { + assert_eq!(pending_responses.len(), i) + } else { + assert_eq!(pending_responses.len(), 0) + } + }) + } +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_handle_message_result_fail_for_invalid_sequence_no() { @@ -284,6 +366,46 @@ fn test_handle_message_result_should_enable_rollback_when_response_is_failure_fr ) } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #15)")] +fn test_handle_message_result_when_invalid_reply_received() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + request.encode(&ctx.env), + ); + let cs_message = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + client.handle_message( + &ctx.centralized_connection, + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_result_when_response_is_success_from_dst_chain() { let ctx = TestContext::default(); @@ -318,6 +440,26 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { &cs_message, ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_message"), + ( + &ctx.centralized_connection, + String::from_str(&ctx.env, "cosmos"), + cs_message + ) + .into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + ctx.env.as_contract(&ctx.contract, || { // rollback should be removed assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); @@ -366,5 +508,66 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { call_msg_event.into_val(&ctx.env) ) ] - ) + ); +} + +#[test] +fn test_handle_error() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.handle_error(&ctx.centralized_connection, &sequence_no); + + let response_msg_event = ResponseMsgEvent { + sn: sequence_no, + code: 0_u32, + }; + let rollback_msg_event = RollbackMsgEvent { sn: sequence_no }; + + let mut events = ctx.env.events().all(); + events.pop_front(); + + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("ResponseMessage",).into_val(&ctx.env), + response_msg_event.into_val(&ctx.env) + ), + ( + client.address.clone(), + ("RollbackMessage",).into_val(&ctx.env), + rollback_msg_event.into_val(&ctx.env) + ) + ] + ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_error"), + (&ctx.centralized_connection, sequence_no,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be enabled + let rollback = storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); } diff --git a/contracts/soroban/contracts/xcall/src/test/send_message.rs b/contracts/soroban/contracts/xcall/src/test/send_message.rs index c46af375..f605046d 100644 --- a/contracts/soroban/contracts/xcall/src/test/send_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/send_message.rs @@ -5,11 +5,11 @@ extern crate std; use soroban_sdk::{ bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, - vec, Address, Bytes, IntoVal, String, + vec, Address, Bytes, IntoVal, String, Vec, }; use soroban_xcall_lib::messages::{ - call_message::CallMessage, call_message_rollback::CallMessageWithRollback, envelope::Envelope, - AnyMessage, + call_message::CallMessage, call_message_persisted::CallMessagePersisted, + call_message_rollback::CallMessageWithRollback, envelope::Envelope, AnyMessage, }; use super::setup::*; @@ -211,6 +211,30 @@ fn test_process_rollback_message_with_empty_rollback_data() { .unwrap(); } +#[test] +fn test_process_persisted_message() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg = CallMessagePersisted { + data: bytes!(&ctx.env, 0xab), + }; + let message = AnyMessage::CallMessagePersisted(msg); + let envelope = &get_dummy_envelope_msg(&ctx.env, message); + + ctx.env.as_contract(&client.address, || { + let res = send_message::process_message( + &ctx.env, + &ctx.network_address, + 1, + &ctx.contract, + envelope, + ); + assert!(res.is_ok()) + }); +} + #[test] fn test_process_rollback_message() { let ctx = TestContext::default(); @@ -304,6 +328,32 @@ fn test_call_connection_for_call_message() { assert_eq!(connection_balance, fee); } +#[test] +fn test_call_connection_with_empty_sources() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + ctx.env.mock_all_auths_allowing_non_root_auth(); + + let sender = Address::generate(&ctx.env); + let need_response = false; + let fee = ctx.get_centralized_connection_fee(need_response); + ctx.mint_native_token(&sender, fee); + + ctx.env.as_contract(&ctx.contract, || { + send_message::call_connection( + &ctx.env, + &sender, + &ctx.nid, + 1, + Vec::new(&ctx.env), + need_response, + Bytes::new(&ctx.env), + ) + .unwrap(); + }) +} + #[test] fn test_calim_protocol_fee() { let ctx = TestContext::default(); diff --git a/contracts/soroban/contracts/xcall/src/test/setup.rs b/contracts/soroban/contracts/xcall/src/test/setup.rs index e4591430..8aa7d49e 100644 --- a/contracts/soroban/contracts/xcall/src/test/setup.rs +++ b/contracts/soroban/contracts/xcall/src/test/setup.rs @@ -10,11 +10,20 @@ use soroban_xcall_lib::{ network_address::NetworkAddress, }; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } +pub mod dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + +pub mod xcall { + soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); +} use crate::{ contract::{Xcall, XcallClient}, @@ -115,6 +124,7 @@ pub struct TestContext { pub token_admin: Address, pub network_address: NetworkAddress, pub upgrade_authority: Address, + pub dapp: Address, pub centralized_connection: Address, } @@ -122,18 +132,21 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let dapp = env.register_contract_wasm(None, dapp::WASM); let centralized_connection = env.register_contract_wasm(None, connection::WASM); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { contract: env.register_contract(None, Xcall), admin: Address::generate(&env), fee_handler: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "stellar"), network_address: get_dummy_network_address(&env), upgrade_authority: Address::generate(&env), env, token_admin, + dapp, centralized_connection, } } @@ -151,6 +164,8 @@ impl TestContext { self.init_connection_state(); client.set_protocol_fee(&100); client.set_default_connection(&self.nid, &self.centralized_connection); + + self.init_dapp_state(); } pub fn init_connection_state(&self) { @@ -160,6 +175,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: self.admin.clone(), xcall_address: self.contract.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); @@ -168,6 +184,17 @@ impl TestContext { connection_client.set_fee(&self.nid, &message_fee, &response_fee); } + pub fn init_dapp_state(&self) { + let dapp_client = dapp::Client::new(&self.env, &self.dapp); + dapp_client.init(&self.admin, &self.contract.clone(), &self.native_token); + + dapp_client.add_connection( + &self.centralized_connection.to_string(), + &Address::generate(&self.env).to_string(), + &self.nid, + ); + } + pub fn mint_native_token(&self, address: &Address, amount: u128) { let native_token_client = token::StellarAssetClient::new(&self.env, &self.native_token); native_token_client.mint(&address, &(*&amount as i128)); diff --git a/contracts/soroban/contracts/xcall/src/types/request.rs b/contracts/soroban/contracts/xcall/src/types/request.rs index cb91857e..4decd4a0 100644 --- a/contracts/soroban/contracts/xcall/src/types/request.rs +++ b/contracts/soroban/contracts/xcall/src/types/request.rs @@ -61,11 +61,6 @@ impl CSMessageRequest { msg_type == MessageType::CallMessageWithRollback } - pub fn allow_retry(&self) -> bool { - let msg_type: MessageType = (self.msg_type as u8).into(); - msg_type == MessageType::CallMessagePersisted - } - pub fn data(&self) -> &Bytes { &self.data } diff --git a/contracts/soroban/contracts/xcall/src/types/storage_types.rs b/contracts/soroban/contracts/xcall/src/types/storage_types.rs index f24d82e7..fec97168 100644 --- a/contracts/soroban/contracts/xcall/src/types/storage_types.rs +++ b/contracts/soroban/contracts/xcall/src/types/storage_types.rs @@ -15,6 +15,7 @@ pub enum StorageKey { PendingResponses(BytesN<32>), LastReqId, UpgradeAuthority, + Version, } #[contracttype] From 84aa843d0ca05c50ec35295235eedef27361b99f Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Wed, 23 Oct 2024 12:31:32 +0545 Subject: [PATCH 45/75] fix: make changes for eth abi --- .../sui/libs/sui_rlp/sources/decoder.move | 6 ++ .../sui/libs/sui_rlp/sources/encoder.move | 37 ++++++------ contracts/sui/libs/sui_rlp/sources/utils.move | 58 +++++++++++-------- 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/contracts/sui/libs/sui_rlp/sources/decoder.move b/contracts/sui/libs/sui_rlp/sources/decoder.move index 3fa18a96..7e794f6b 100644 --- a/contracts/sui/libs/sui_rlp/sources/decoder.move +++ b/contracts/sui/libs/sui_rlp/sources/decoder.move @@ -151,5 +151,11 @@ module sui_rlp::decoder { bcs::peel_address(&mut bcs) } + public fun decode_bool(vec:&vector):bool{ + let val= *vector::borrow(vec,0); + val==1 + + } + } \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index 88c43c3d..4ea80dbe 100644 --- a/contracts/sui/libs/sui_rlp/sources/encoder.move +++ b/contracts/sui/libs/sui_rlp/sources/encoder.move @@ -48,25 +48,19 @@ module sui_rlp::encoder { let total_length = result.length(); let len=vector::length(&result); - if( total_length<= 55){ - encoded_list=encode_length(len,0xc0); - vector::append(&mut encoded_list,result); + if( total_length<= 55){ + encoded_list=encode_length(len,0xc0); + vector::append(&mut encoded_list,result); - } else { - let length_bytes = utils::to_bytes_u64(len); - let prefix = (0xf7 + vector::length(&length_bytes)) as u8; - //std::debug::print(&b"PREFIX".to_string()); - //std::debug::print(&prefix); - vector::push_back(&mut encoded_list, prefix); - //std::debug::print(&encoded_list); - vector::append(&mut encoded_list, length_bytes); - //std::debug::print(&encoded_list); + } else { + let length_bytes = utils::to_bytes_u64(len); + let prefix = (0xf7 + vector::length(&length_bytes)) as u8; + vector::push_back(&mut encoded_list, prefix); + vector::append(&mut encoded_list, length_bytes); + vector::append(&mut encoded_list, result); - vector::append(&mut encoded_list, result); - //std::debug::print(&encoded_list); - - } + } }else{ vector::push_back(&mut encoded_list,0xc0); @@ -132,8 +126,11 @@ module sui_rlp::encoder { let vec= bcs::to_bytes(addr); encode(&vec) } -} - - - + public fun encode_bool(val:bool):vector{ + if(val==true){ + return vector[1] + }; + vector[0] + } +} \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/utils.move b/contracts/sui/libs/sui_rlp/sources/utils.move index f73f0472..d222da66 100644 --- a/contracts/sui/libs/sui_rlp/sources/utils.move +++ b/contracts/sui/libs/sui_rlp/sources/utils.move @@ -1,6 +1,7 @@ module sui_rlp::utils { use std::vector::{Self}; use std::string::{Self,String}; + use std::bcs; public fun to_bytes_u32(number: u32): vector { let mut bytes: vector = vector::empty(); let mut i:u8=0; @@ -33,20 +34,6 @@ module sui_rlp::utils { result } - public fun to_bytes_u64(number: u64): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; - while(i < 8){ - let val =( (number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); - i=i+1; - }; - bytes.reverse(); - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix - } - fun truncate_zeros(bytes: &vector): vector { let mut i = 0; let mut started = false; @@ -85,21 +72,42 @@ module sui_rlp::utils { result } - - - // Convert u128 to bytes - public fun to_bytes_u128(number: u128): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; + public fun to_bytes_u128(number:u128):vector{ + let mut right:u128= 128; + let mut i=1; while(i < 16){ - let val = ((number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); + right=right << 8; + i=i+1; + + }; + let mut bytes=bcs::to_bytes(&number); + bytes.reverse(); + if (number < right){ + truncate_zeros(&bytes) + }else { + let mut prefix = vector[0]; + prefix.append(truncate_zeros(&bytes)); + prefix + } + } + + public fun to_bytes_u64(number:u64):vector{ + let mut right:u64= 128; + let mut i=1; + while(i < 8){ + right=right << 8; i=i+1; + }; + let mut bytes=bcs::to_bytes(&number); bytes.reverse(); - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix + if (number < right){ + truncate_zeros(&bytes) + }else { + let mut prefix = vector[0]; + prefix.append(truncate_zeros(&bytes)); + prefix + } } // Convert bytes to u128 From 359651896b805d82ecdaa8ad9e0852a459c747fb Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Wed, 23 Oct 2024 12:53:18 +0545 Subject: [PATCH 46/75] fix: fix tests --- contracts/sui/xcall/sources/types/message_result.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/sui/xcall/sources/types/message_result.move b/contracts/sui/xcall/sources/types/message_result.move index 0f53df48..9130009c 100644 --- a/contracts/sui/xcall/sources/types/message_result.move +++ b/contracts/sui/xcall/sources/types/message_result.move @@ -87,7 +87,7 @@ module xcall::message_result_tests { let msg= message_result::create(1,message_result::success(),vector::empty()); let encoded= message_result::encode(&msg); std::debug::print(&encoded); - assert!(encoded==x"c58200010180",0x01); + assert!(encoded==x"c3010180",0x01); let decoded=message_result::decode(&encoded); assert!(decoded==msg,0x01); @@ -99,7 +99,7 @@ module xcall::message_result_tests { let msg= message_result::create(2,message_result::failure(),vector::empty()); let encoded= message_result::encode(&msg); std::debug::print(&encoded); - assert!(encoded==x"c58200020080",0x01); + assert!(encoded==x"c3020080",0x01); let decoded=message_result::decode(&encoded); assert!(decoded==msg,0x01); From dfeb7cc2155d235f4fc801445f8790e4998f80b7 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Thu, 24 Oct 2024 21:10:34 +0545 Subject: [PATCH 47/75] fix: sui eth-rlp fixes --- .../sui/libs/sui_rlp/sources/decoder.move | 1 - .../sui/libs/sui_rlp/sources/encoder.move | 30 ++- contracts/sui/libs/sui_rlp/sources/utils.move | 193 +++++++----------- .../sui/libs/sui_rlp/tests/rlp_tests.move | 74 +++++++ 4 files changed, 159 insertions(+), 139 deletions(-) diff --git a/contracts/sui/libs/sui_rlp/sources/decoder.move b/contracts/sui/libs/sui_rlp/sources/decoder.move index 7e794f6b..acd27d51 100644 --- a/contracts/sui/libs/sui_rlp/sources/decoder.move +++ b/contracts/sui/libs/sui_rlp/sources/decoder.move @@ -20,7 +20,6 @@ module sui_rlp::decoder { } else { let length_len = byte - 0xb7; let length_bytes = utils::slice_vector(encoded, 1, length_len as u64); - //debug::print(&length_bytes); let length = utils::from_bytes_u64(&length_bytes); let data_start = (length_len + 1) as u64; utils::slice_vector(encoded, data_start, length) diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index 4ea80dbe..ea0dadff 100644 --- a/contracts/sui/libs/sui_rlp/sources/encoder.move +++ b/contracts/sui/libs/sui_rlp/sources/encoder.move @@ -21,14 +21,11 @@ module sui_rlp::encoder { vector::append(&mut result,*bytes); result }; - //std::debug::print(&encoded); encoded } public fun encode_list(list:&vector>,raw:bool):vector{ - //std::debug::print(&b"ENCODELIST".to_string()); - //std::debug::print(list); let mut result=vector::empty(); let mut encoded_list = vector::empty(); let mut list=*list; @@ -40,7 +37,6 @@ module sui_rlp::encoder { vector::append(&mut result,encode(&vector::pop_back(&mut list))); }else{ vector::append(&mut result,vector::pop_back(&mut list)); - //std::debug::print(&result); }; }; @@ -53,21 +49,17 @@ module sui_rlp::encoder { vector::append(&mut encoded_list,result); } else { - let length_bytes = utils::to_bytes_u64(len); - let prefix = (0xf7 + vector::length(&length_bytes)) as u8; - vector::push_back(&mut encoded_list, prefix); - vector::append(&mut encoded_list, length_bytes); - vector::append(&mut encoded_list, result); - - + let length_bytes = utils::to_bytes_u64(len); + let prefix = (0xf7 + vector::length(&length_bytes)) as u8; + vector::push_back(&mut encoded_list, prefix); + vector::append(&mut encoded_list, length_bytes); + vector::append(&mut encoded_list, result); } }else{ vector::push_back(&mut encoded_list,0xc0); }; - //std::debug::print(&b"FINAL_ENCODED_LIST".to_string()); - //std::debug::print(&encoded_list); encoded_list } @@ -77,11 +69,11 @@ 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); - 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); - vector::append(&mut length_info,length_bytes); + let length_bytes=utils::to_bytes_u64(len); + 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); + vector::append(&mut length_info,length_bytes); }; length_info } @@ -133,4 +125,6 @@ module sui_rlp::encoder { }; vector[0] } + + } \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/utils.move b/contracts/sui/libs/sui_rlp/sources/utils.move index d222da66..e09d9d88 100644 --- a/contracts/sui/libs/sui_rlp/sources/utils.move +++ b/contracts/sui/libs/sui_rlp/sources/utils.move @@ -1,139 +1,99 @@ module sui_rlp::utils { - use std::vector::{Self}; - use std::string::{Self,String}; - use std::bcs; - public fun to_bytes_u32(number: u32): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; - while(i < 4){ - let val =( (number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); - i=i+1; - }; - bytes.reverse(); - bytes - } + use std::bcs; + // Convert bytes to u32 - public fun from_bytes_u32(bytes: &vector): u32 {let mut result = 0; - let mut multiplier = 1; - let length = vector::length(bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(bytes, i) as u32) * multiplier); - //std::debug::print(&result); - - if (i > 0) { - multiplier = multiplier * 256 - }; - + public fun from_bytes_u32(bytes: &vector): u32 { + let mut bytes= truncate_zeros(bytes); + bytes.reverse(); + let mut diff= 4-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; }; - result + sui::bcs::peel_u32(&mut sui::bcs::new(bytes)) } - fun truncate_zeros(bytes: &vector): vector { - let mut i = 0; - let mut started = false; - let mut result: vector = vector::empty(); - while (i < vector::length(bytes)) { - let val = *vector::borrow(bytes, i) as u8; - if (val > 0 || started) { - started = true; - vector::push_back(&mut result, val); - }; - i = i + 1; + // Convert bytes to u64 + public fun from_bytes_u64(bytes: &vector): u64 { + let mut bytes= truncate_zeros(bytes); + bytes.reverse(); + let mut diff= 8-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; }; + sui::bcs::peel_u64(&mut sui::bcs::new(bytes)) - result } - // Convert bytes to u64 - public fun from_bytes_u64(bytes: &vector): u64 { - let bytes = truncate_zeros(bytes); - let mut result = 0; - let mut multiplier = 1; - let length = vector::length(&bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(&bytes, i) as u64) * (multiplier)); - //std::debug::print(&result); - if (i > 0) { - multiplier = multiplier * 256 - }; - + // Convert bytes to u128 + public fun from_bytes_u128(bytes: &vector): u128 { + let mut bytes= truncate_zeros(bytes); + bytes.reverse(); + let mut diff= 16-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; }; - result + sui::bcs::peel_u128(&mut sui::bcs::new(bytes)) + } public fun to_bytes_u128(number:u128):vector{ - let mut right:u128= 128; - let mut i=1; - while(i < 16){ - right=right << 8; - i=i+1; - - }; - let mut bytes=bcs::to_bytes(&number); - bytes.reverse(); - if (number < right){ - truncate_zeros(&bytes) - }else { - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix - } + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes) } + public fun to_bytes_u64(number:u64):vector{ - let mut right:u64= 128; - let mut i=1; - while(i < 8){ - right=right << 8; - i=i+1; + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes) + } - }; - let mut bytes=bcs::to_bytes(&number); + public fun to_bytes_u32(number: u32): vector { + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes) + } + + fun to_signed_bytes(mut bytes:vector):vector{ bytes.reverse(); - if (number < right){ - truncate_zeros(&bytes) + let truncated=truncate_zeros(&bytes); + let first_byte=*truncated.borrow(0); + + if (first_byte >= 128) { + let mut prefix = vector[0]; + prefix.append(truncated); + prefix + }else { - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix + truncated } + } - // Convert bytes to u128 - public fun from_bytes_u128(bytes: &vector): u128 { - let bytes = truncate_zeros(bytes); - let mut result = 0; - let mut multiplier = 1; - let length = vector::length(&bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(&bytes, i) as u128) * multiplier); - //std::debug::print(&result); - - if (i > 0) { - multiplier = multiplier * 256 + fun truncate_zeros(bytes: &vector): vector { + let mut i = 0; + let mut started = false; + let mut result: vector = vector::empty(); + while (i < vector::length(bytes)) { + let val = *vector::borrow(bytes, i) as u8; + if (val > 0 || started) { + started = true; + vector::push_back(&mut result, val); }; - + + i = i + 1; }; + result } + + /* end is exclusive in slice*/ - public fun slice_vector(vec: &vector, start: u64, length: u64): vector { + public fun slice_vector(vec: &vector, start: u64, length: u64): vector { let mut result = vector::empty(); let mut i = 0; while (i < length) { @@ -141,27 +101,23 @@ module sui_rlp::utils { vector::push_back(&mut result, value); i = i + 1; }; - //std::debug::print(&result); result } - + } module sui_rlp::utils_test { use sui_rlp::utils::{Self}; - use std::vector::{Self}; - use std::debug; - use sui::bcs::{Self}; - #[test] + #[test] fun test_u32_conversion() { let num= (122 as u32); let bytes= utils::to_bytes_u32(num); let converted=utils::from_bytes_u32(&bytes); assert!(num==converted,0x01); - + } #[test] @@ -172,7 +128,7 @@ module sui_rlp::utils_test { std::debug::print(&bytes); std::debug::print(&converted); assert!(num==converted,0x01); - + } #[test] @@ -183,7 +139,7 @@ module sui_rlp::utils_test { let converted=utils::from_bytes_u128(&bytes); std::debug::print(&converted); assert!(num==converted,0x01); - + } #[test] @@ -191,16 +147,13 @@ module sui_rlp::utils_test { let data=create_vector(10); let slice= utils::slice_vector(&data,0,3); let expected= create_vector(3); - //debug::print(&expected); - //debug::print(&slice); - //debug::print(&data); assert!(slice==expected,0x01); - + } fun create_vector(n:u8):vector{ - let mut data=vector::empty(); + let mut data=vector::empty(); let mut i=0; while(i < n){ vector::push_back(&mut data,i); diff --git a/contracts/sui/libs/sui_rlp/tests/rlp_tests.move b/contracts/sui/libs/sui_rlp/tests/rlp_tests.move index 2632c074..11ded18d 100644 --- a/contracts/sui/libs/sui_rlp/tests/rlp_tests.move +++ b/contracts/sui/libs/sui_rlp/tests/rlp_tests.move @@ -80,6 +80,80 @@ module sui_rlp::rlp_tests { list } + + #[test] + fun test_encoding_u128(){ + let num:u128=100; + let bytes=x"64"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + //// + /// + let num:u128=200; + let bytes=x"8200c8"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + /// + /// + let num:u128=3000000; + let bytes=x"832dc6c0"; + let encoded= encoder::encode_u128(num); + std::debug::print(&encoded); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=273468273; + let bytes=x"84104ccb71"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=2342312; + let bytes=x"8323bda8"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=1233; + let bytes=x"8204d1"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=412926; + let bytes=x"83064cfe"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=9434628989898; + let bytes=x"860894abb5a3ca"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=92625222222121112; + let bytes=x"88014912261bca8898"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + + + } + From 4c8b626d7b64caee2c8212f7772ca836797610dc Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Fri, 25 Oct 2024 09:03:56 +0545 Subject: [PATCH 48/75] fix: cluster connection fix --- .../adapter/cluster/ClusterConnection.java | 13 +----- .../cluster/ClusterConnectionTest.java | 43 +++---------------- 2 files changed, 7 insertions(+), 49 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index bf8103ed..9c358cf2 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -81,7 +81,7 @@ public Address[] listValidators() { * @throws Exception if the number of validators is less than the threshold */ @External - public void addValidator(Address[] _validators, BigInteger _threshold) { + public void updateValidators(Address[] _validators, BigInteger _threshold) { OnlyAdmin(); clearValidators(); for (Address validator : _validators) { @@ -266,16 +266,7 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt recvMessage(srcNetwork, _connSn, msg); } - /** - * Receives a message from a source network. - * - * @param srcNetwork the source network id from which the message is received - * @param _connSn the serial number of the connection message - * @param msg serialized bytes of Service Message - */ - @External - public void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { - OnlyRelayer(); + private void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.require(!receipts.at(srcNetwork).getOrDefault(_connSn, false), "Duplicate Message"); receipts.at(srcNetwork).set(_connSn, true); Context.call(xCall.get(), "handleMessage", srcNetwork, msg); diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 914272bc..f165323a 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -95,19 +95,6 @@ public void sendMessage() { verify(connectionSpy).Message(nidTarget, BigInteger.ONE, "test".getBytes()); } - @Test - public void testRecvMessage() { - connection.invoke(source_relayer, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes()); - verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); - } - - @Test - public void testRecvMessage_unauthorized(){ - - UserRevertedException e = assertThrows(UserRevertedException.class, ()-> connection.invoke(xcallMock, "recvMessage", nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals("Reverted(0): "+"Only relayer can call this function", e.getMessage()); - } - @Test public void testSendMessage_unauthorized() { UserRevertedException e = assertThrows(UserRevertedException.class, @@ -115,15 +102,6 @@ public void testSendMessage_unauthorized() { assertEquals("Reverted(0): " + "Only xCall can send messages", e.getMessage()); } - @Test - public void testRecvMessage_duplicateMsg(){ - connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); - - UserRevertedException e = assertThrows(UserRevertedException.class,() -> connection.invoke(source_relayer, "recvMessage", - nidSource, BigInteger.ONE, "test".getBytes())); - assertEquals(e.getMessage(), "Reverted(0): "+"Duplicate Message"); - } - @Test public void testRevertMessage() { @@ -178,17 +156,6 @@ public MockedStatic.Verification value() { return () -> Context.getValue(); } - @Test - public void testGetReceipt(){ - assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), - false); - - connection.invoke(source_relayer, "recvMessage",nidSource, BigInteger.ONE, "test".getBytes()); - - assertEquals(connection.call("getReceipts", nidSource, BigInteger.ONE), - true); - } - @Test public void testRecvMessageWithSignatures() throws Exception{ byte[] data = "test".getBytes(); @@ -197,7 +164,7 @@ public void testRecvMessageWithSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.ONE); + connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -212,7 +179,7 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } @@ -225,7 +192,7 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough signatures", e.getMessage()); @@ -241,7 +208,7 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); assertEquals("Reverted(0): Not enough valid signatures", e.getMessage()); @@ -262,7 +229,7 @@ public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[ public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; - connection.invoke(owner, "addValidator", validators, BigInteger.TWO); + connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); Address[] signers = connection.call(Address[].class,"listValidators"); assertEquals(signers.length, 2); } From 8af3ecd7c145e962a49432aa18424b2d81bf9711 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Fri, 25 Oct 2024 13:31:55 +0545 Subject: [PATCH 49/75] fix: stellar cluster conenction --- .../cluster-connection/src/contract.rs | 65 +++++-------------- .../cluster-connection/src/helpers.rs | 14 +++- .../cluster-connection/src/storage.rs | 14 ---- .../contracts/cluster-connection/src/test.rs | 59 ++++------------- 4 files changed, 40 insertions(+), 112 deletions(-) diff --git a/contracts/soroban/contracts/cluster-connection/src/contract.rs b/contracts/soroban/contracts/cluster-connection/src/contract.rs index a8d329f6..86377d61 100644 --- a/contracts/soroban/contracts/cluster-connection/src/contract.rs +++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs @@ -69,30 +69,6 @@ impl ClusterConnection { Ok(()) } - pub fn recv_message( - env: Env, - src_network: String, - conn_sn: u128, - msg: Bytes, - ) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; - - 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 revert_message(env: &Env, sn: u128) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; - helpers::call_xcall_handle_error(&env, sn)?; - - Ok(()) - } - pub fn recv_message_with_signatures( env: Env, src_network: String, @@ -102,7 +78,7 @@ impl ClusterConnection { ) -> Result<(), ContractError> { helpers::ensure_relayer(&env)?; - if !helpers::verify_signatures(&env, signatures, msg.clone()){ + if !helpers::verify_signatures(&env, signatures, &src_network, &conn_sn, &msg){ return Err(ContractError::SignatureVerificationFailed); }; @@ -138,13 +114,20 @@ impl ClusterConnection { Ok(()) } - pub fn add_validator(env: Env, address: Address) -> Result<(), ContractError> { + pub fn update_validators(env: Env, addresses: Vec
, threshold: u32) -> Result<(), ContractError> { helpers::ensure_relayer(&env)?; - let validators = storage::get_validators(&env).unwrap(); - if validators.contains(&address) { - return Err(ContractError::ValidatorAlreadyAdded); + let mut validators = Vec::new(&env); + + for address in addresses.clone() { + if !validators.contains(&address) { + validators.push_back(address); + } } - storage::add_validator(&env, address); + if (validators.len() as u32) < threshold { + return Err(ContractError::ThresholdExceeded); + + } + storage::store_validators(&env, addresses); Ok(()) } @@ -155,27 +138,11 @@ impl ClusterConnection { pub fn set_validators_threshold(env: Env, threshold: u32) -> Result<(), ContractError> { helpers::ensure_relayer(&env)?; - storage::store_validator_threshold(&env, threshold); - Ok(()) - } - - pub fn remove_validator(env: Env, address: Address) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; - let validators: Vec
= storage::get_validators(&env).unwrap(); - let threshold = storage::get_validators_threshold(&env).unwrap(); - let admin = storage::admin(&env).unwrap(); - - let index = validators.iter().position(|v| v == address); - if index.is_none() { - return Err(ContractError::ValidatorNotFound); - } - if address == admin { - return Err(ContractError::CannotRemoveAdmin); - } - if (validators.len() as u32) <= threshold { + let validators = storage::get_validators(&env).unwrap(); + if (validators.len() as u32) < threshold { return Err(ContractError::ThresholdExceeded); } - storage::remove_validator(&env, address); + storage::store_validator_threshold(&env, threshold); Ok(()) } diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs index eb823868..40a930cc 100644 --- a/contracts/soroban/contracts/cluster-connection/src/helpers.rs +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -80,7 +80,9 @@ pub fn compress_public_keys(e: &Env, uncompressed_public_key: BytesN<65>) -> Byt pub fn verify_signatures( e: &Env, signatures: Vec>, - message: Bytes, + src_network: &String, + conn_sn: &u128, + message: &Bytes, ) -> bool { let validators = storage::get_validators(e).unwrap(); let threshold = storage::get_validators_threshold(e).unwrap(); @@ -88,14 +90,13 @@ pub fn verify_signatures( if signatures.len() < threshold { return false } - let message_hash = e.crypto().sha256(&message); + 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_id = r_s_v[64] as u32; // recovery ID @@ -120,6 +121,13 @@ pub fn verify_signatures( } +pub fn get_encoded_message(e: &Env, src_network: &String, conn_sn: &u128, message: &Bytes) -> Bytes { + let mut result = Bytes::from_val(e, &src_network.to_val()); + result.extend_from_slice(&conn_sn.to_be_bytes()); + result.append(message); + result +} + 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); diff --git a/contracts/soroban/contracts/cluster-connection/src/storage.rs b/contracts/soroban/contracts/cluster-connection/src/storage.rs index 3e98e731..538b21dd 100644 --- a/contracts/soroban/contracts/cluster-connection/src/storage.rs +++ b/contracts/soroban/contracts/cluster-connection/src/storage.rs @@ -164,20 +164,6 @@ pub fn store_validators(e: &Env, validators: Vec
) { e.storage().instance().set(&StorageKey::Validators, &validators); } -pub fn remove_validator(e: &Env, address: Address) { - let mut validators: Vec
= get_validators(e).unwrap(); - if let Some(index) = validators.iter().position(|v| v == address) { - validators.remove(index.try_into().unwrap()); - e.storage().instance().set(&StorageKey::Validators, &validators); - } -} - -pub fn add_validator(e: &Env, address: Address) { - let mut validators = get_validators(e).unwrap(); - validators.push_back(address); - store_validators(e, 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 { diff --git a/contracts/soroban/contracts/cluster-connection/src/test.rs b/contracts/soroban/contracts/cluster-connection/src/test.rs index 17470c70..bab627e0 100644 --- a/contracts/soroban/contracts/cluster-connection/src/test.rs +++ b/contracts/soroban/contracts/cluster-connection/src/test.rs @@ -353,11 +353,16 @@ fn test_add_validator() { ctx.init_context(&client); - let validator = Address::generate(&ctx.env); - client.add_validator(&validator.clone()); + let validator1 = Address::generate(&ctx.env); + let validator2 = Address::generate(&ctx.env); + let validator3 = Address::generate(&ctx.env); let mut validators = Vec::new(&ctx.env); - validators.push_back(validator.clone()); + validators.push_back(validator1.clone()); + validators.push_back(validator2.clone()); + validators.push_back(validator3.clone()); + + client.update_validators(&validators, &3_u32); assert_eq!( ctx.env.auths(), @@ -366,27 +371,23 @@ fn test_add_validator() { AuthorizedInvocation { function: AuthorizedFunction::Contract(( client.address.clone(), - Symbol::new(&ctx.env, "add_validator"), - (validator.clone(),).into_val(&ctx.env) + Symbol::new(&ctx.env, "update_validators"), + (validators.clone(), 3,).into_val(&ctx.env) )), sub_invocations: std::vec![] } )] ); - let validator = Address::generate(&ctx.env); - client.add_validator(&validator.clone()); - validators.push_back(validator.clone()); - - assert_eq!( client.get_validators(), validators ); } + #[test] -fn test_remove_validator() { +fn test_set_threshold() { let ctx = TestContext::default(); let client = ClusterConnectionClient::new(&ctx.env, &ctx.contract); @@ -396,46 +397,12 @@ fn test_remove_validator() { let validator2 = Address::generate(&ctx.env); let validator3 = Address::generate(&ctx.env); - client.add_validator(&validator1.clone()); - client.add_validator(&validator2.clone()); - client.add_validator(&validator3.clone()); - let mut validators = Vec::new(&ctx.env); validators.push_back(validator1.clone()); validators.push_back(validator2.clone()); validators.push_back(validator3.clone()); - assert_eq!( - client.get_validators(), - validators - ); - - assert_eq!( - client.get_validators().len(), - 3 - ); - - client.remove_validator(&validator2.clone()); - - assert_eq!( - client.get_validators().len(), - 2 - ); - validators.remove(1); - - 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); - + client.update_validators(&validators, &3_u32); let threshold: u32 = 2_u32; client.set_validators_threshold(&threshold); From 7034223ef5e2295a118efbd346cdfd97c87f191c Mon Sep 17 00:00:00 2001 From: sherpalden Date: Fri, 25 Oct 2024 14:05:53 +0545 Subject: [PATCH 50/75] fix: add setRelayers method --- .../relay/aggregator/RelayAggregator.java | 59 +++++---- .../relay/aggregator/RelayAggregatorTest.java | 125 +++++++++++------- 2 files changed, 113 insertions(+), 71 deletions(-) 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 f4f41542..ec475a99 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -29,6 +29,7 @@ import score.annotation.External; import score.ObjectReader; import scorex.util.ArrayList; +import scorex.util.HashMap; public class RelayAggregator { private final Integer DEFAULT_SIGNATURE_THRESHOLD = 1; @@ -57,6 +58,8 @@ public RelayAggregator(Address _admin) { public void setAdmin(Address _admin) { adminOnly(); + Context.require(admin.get() != _admin, "admin already set"); + // add new admin as relayer addRelayer(_admin); @@ -74,7 +77,8 @@ public Address getAdmin() { @External public void setSignatureThreshold(int threshold) { adminOnly(); - Context.require(threshold >= 1, "invalid threshold value: should be >= 1"); + Context.require(threshold > 0 && threshold <= relayers.size(), + "threshold value should be at least 1 and not greater than relayers size"); signatureThreshold.set(threshold); } @@ -93,30 +97,29 @@ public Address[] getRelayers() { } @External - public void addRelayers(Address[] newRelayers) { + public void setRelayers(Address[] newRelayers, int threshold) { adminOnly(); - Context.require(newRelayers != null && newRelayers.length != 0, "new relayers cannot be empty"); - - for (Address newRelayer : newRelayers) { - Boolean exits = relayersLookup.get(newRelayer); - if (exits == null) { + if (newRelayers.length > 0) { + HashMap newRelayersMap = new HashMap(); + for (Address newRelayer : newRelayers) { + newRelayersMap.put(newRelayer, true); addRelayer(newRelayer); } - } - } - @External - public void removeRelayers(Address[] relayersToBeRemoved) { - adminOnly(); + Address adminAdrr = admin.get(); + for (int i = 0; i < relayers.size(); i++) { + Address oldRelayer = relayers.get(i); + if (!oldRelayer.equals(adminAdrr) && !newRelayersMap.containsKey(oldRelayer)) { + removeRelayer(oldRelayer); + } + } + } - Context.require(relayersToBeRemoved != null && relayersToBeRemoved.length != 0, - "relayers to be removed cannot be empty"); + Context.require(threshold > 0 && threshold <= relayers.size(), + "threshold value should be at least 1 and not greater than relayers size"); - for (Address relayerToBeRemoved : relayersToBeRemoved) { - Context.require(relayerToBeRemoved != admin.get(), "admin cannot be removed from relayers list"); - removeRelayer(relayerToBeRemoved); - } + signatureThreshold.set(threshold); } @External(readonly = true) @@ -248,17 +251,21 @@ private void relayersOnly() { } private void addRelayer(Address newRelayer) { - relayers.add(newRelayer); - relayersLookup.set(newRelayer, true); + if (relayersLookup.get(newRelayer) == null) { + relayers.add(newRelayer); + relayersLookup.set(newRelayer, true); + } } private void removeRelayer(Address oldRelayer) { - relayersLookup.set(oldRelayer, null); - Address top = relayers.pop(); - for (int i = 0; i < relayers.size(); i++) { - if (oldRelayer.equals(relayers.get(i))) { - relayers.set(i, top); - break; + if (relayersLookup.get(oldRelayer)) { + relayersLookup.set(oldRelayer, null); + Address top = relayers.pop(); + for (int i = 0; i < relayers.size(); i++) { + if (oldRelayer.equals(relayers.get(i))) { + relayers.set(i, top); + break; + } } } } 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 d44e84a2..0da3391f 100644 --- a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -10,6 +10,7 @@ import score.Address; import score.Context; import score.UserRevertedException; +import scorex.util.HashSet; import com.iconloop.score.test.Account; import com.iconloop.score.test.Score; @@ -69,7 +70,7 @@ void setup() throws Exception { Address[] relayers = new Address[] { adminAc.getAddress(), relayerOneAc.getAddress(), relayerTwoAc.getAddress(), relayerThreeAc.getAddress() }; - aggregator.invoke(adminAc, "addRelayers", (Object) relayers); + aggregator.invoke(adminAc, "setRelayers", (Object) relayers, 2); aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); aggregator.setInstance(aggregatorSpy); @@ -118,68 +119,91 @@ public void testSetSignatureThreshold() { public void testSetSignatureThreshold_unauthorised() { int threshold = 3; - Executable action = () -> aggregator.invoke(relayerOneAc, "setSignatureThreshold", threshold); + Executable action = () -> aggregator.invoke(relayerOneAc, + "setSignatureThreshold", threshold); 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 leader relayer", + e.getMessage()); } @Test - public void testAddRelayers() { + public void testSetRelayers() { Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerFourAc.getAddress(), relayerFiveAc.getAddress() }; + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - aggregator.invoke(adminAc, "addRelayers", (Object) newRelayers); + Integer threshold = 3; + aggregator.invoke(adminAc, "setRelayers", (Object) newRelayers, threshold); Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); - assertTrue(updatedRelayers[updatedRelayers.length - 1].equals(relayerFiveAc.getAddress())); - assertTrue(updatedRelayers[updatedRelayers.length - 2].equals(relayerFourAc.getAddress())); + Address[] expectedRelayers = new Address[] { adminAc.getAddress(), relayerThreeAc.getAddress(), + relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + HashSet
updatedRelayersSet = new HashSet
(); + for (Address rlr : updatedRelayers) { + updatedRelayersSet.add(rlr); + } + + HashSet
expectedRelayersSet = new HashSet
(); + for (Address rlr : expectedRelayers) { + expectedRelayersSet.add(rlr); + } + + assertEquals(expectedRelayersSet, updatedRelayersSet); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); } @Test - public void testAddRelayers_unauthorised() { + public void testSetRelayers_unauthorized() { Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerFourAc.getAddress(), relayerFiveAc.getAddress() }; + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - Executable action = () -> aggregator.invoke(relayerOneAc, "addRelayers", (Object) newRelayers); + Integer threshold = 3; + Executable action = () -> aggregator.invoke(relayerOneAc, "setRelayers", + (Object) newRelayers, threshold); 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 leader relayer", + e.getMessage()); + } @Test - public void testRemoveRelayers() { - Address[] relayerToBeRemoved = new Address[] { relayerOneAc.getAddress(), - relayerTwoAc.getAddress() }; - - aggregator.invoke(adminAc, "removeRelayers", (Object) relayerToBeRemoved); + public void testSetRelayers_invalidThreshold() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); + Integer threshold = 5; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); - Boolean removed = true; - for (Address rlr : updatedRelayers) { - if (rlr.equals(relayerOneAc.getAddress()) || rlr.equals(relayerTwoAc.getAddress())) { - removed = false; - break; - } - } + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); - assertTrue(removed); - assertEquals(updatedRelayers[1], relayerThreeAc.getAddress()); } @Test - public void testRemoveRelayers_unauthorised() { - Address[] relayerToBeRemoved = new Address[] { relayerOneAc.getAddress(), - relayerTwoAc.getAddress() }; - - aggregator.invoke(adminAc, "removeRelayers", (Object) relayerToBeRemoved); + public void testSetRelayers_invalidThresholdZero() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - Executable action = () -> aggregator.invoke(relayerFourAc, "removeRelayers", (Object) relayerToBeRemoved); + Integer threshold = 0; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); + } @Test @@ -197,11 +221,13 @@ public void testPacketSubmitted_true() throws Exception { byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data, sign); - boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, + boolean submitted = (boolean) aggregator.call("packetSubmitted", + relayerOneAc.getAddress(), srcNetwork, srcContractAddress, srcSn); assertEquals(submitted, true); } @@ -212,7 +238,8 @@ public void testPacketSubmitted_false() throws Exception { BigInteger srcSn = BigInteger.ONE; String srcContractAddress = "hxjuiod"; - boolean submitted = (boolean) aggregator.call("packetSubmitted", relayerOneAc.getAddress(), srcNetwork, + boolean submitted = (boolean) aggregator.call("packetSubmitted", + relayerOneAc.getAddress(), srcNetwork, srcContractAddress, srcSn); assertEquals(submitted, false); } @@ -232,12 +259,14 @@ public void testSubmitPacket() throws Exception { byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + 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, + verify(aggregatorSpy).PacketRegistered(srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, dstContractAddress, data); verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); } @@ -257,12 +286,14 @@ 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, + 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, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data, signOne); @@ -277,7 +308,8 @@ public void testSubmitPacket_thresholdReached() throws Exception { assertArrayEquals(signAdmin, decodedSigs[0]); assertArrayEquals(signOne, decodedSigs[1]); - verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, + srcSn, srcHeight, dstNetwork, dstContractAddress, data, encodedSigs); } @@ -295,7 +327,8 @@ public void testSubmitPacket_unauthorized() throws Exception { byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerFour.sign(dataHash); - Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", srcNetwork, srcContractAddress, + Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", + srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data, sign); @@ -319,10 +352,12 @@ public void testSubmitPacket_duplicate() throws Exception { byte[] dataHash = Context.hash("sha-256", data); byte[] sign = relayerOne.sign(dataHash); - aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data, sign); - Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, srcContractAddress, srcSn, + Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", + srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data, sign); ; From cb100095549103b526aebaa5b164d0b3d56b95d8 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Fri, 25 Oct 2024 14:39:01 +0545 Subject: [PATCH 51/75] fix: be to le bytes --- contracts/soroban/contracts/cluster-connection/src/helpers.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs index 40a930cc..f34d968e 100644 --- a/contracts/soroban/contracts/cluster-connection/src/helpers.rs +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -60,7 +60,6 @@ pub fn transfer_token( pub fn compress_public_keys(e: &Env, uncompressed_public_key: BytesN<65>) -> Bytes { let uncompressed_pub_key_array = uncompressed_public_key.to_array(); if uncompressed_pub_key_array[0] != 0x04 { - //return empty bytessize(33); return Bytes::from_array(e, &[0u8; 33]); } @@ -123,7 +122,7 @@ pub fn verify_signatures( pub fn get_encoded_message(e: &Env, src_network: &String, conn_sn: &u128, message: &Bytes) -> Bytes { let mut result = Bytes::from_val(e, &src_network.to_val()); - result.extend_from_slice(&conn_sn.to_be_bytes()); + result.extend_from_slice(&conn_sn.to_le_bytes()); result.append(message); result } From 9c03a0b4e8f60baac53d186e7742e043ee3ffbc1 Mon Sep 17 00:00:00 2001 From: gcranju <134275268+gcranju@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:16:17 +0545 Subject: [PATCH 52/75] feat: sui cluster connection (#393) * feat: sui cluster connection * feat: added tests for cluster state * fix: unique pubkeys verification * fix: sui-cluster-connection-spec-change * fix: cluster connection admin cap --------- Co-authored-by: gcranju <075bct064.ranju@pcampus.edu.np> Co-authored-by: ibrizsabin --- .../cluster_connection.move | 54 +++ .../cluster_connection/cluster_entry.move | 65 ++++ .../cluster_connection/cluster_state.move | 340 ++++++++++++++++++ .../cluster_connection/signatures.move | 196 ++++++++++ contracts/sui/xcall/sources/connections.move | 32 +- contracts/sui/xcall/sources/utils.move | 13 +- 6 files changed, 691 insertions(+), 9 deletions(-) create mode 100644 contracts/sui/xcall/sources/cluster_connection/cluster_connection.move create mode 100644 contracts/sui/xcall/sources/cluster_connection/cluster_entry.move create mode 100644 contracts/sui/xcall/sources/cluster_connection/cluster_state.move create mode 100644 contracts/sui/xcall/sources/cluster_connection/signatures.move diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move b/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move new file mode 100644 index 00000000..0f72f53a --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move @@ -0,0 +1,54 @@ +#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] +module xcall::cluster_connection { + use xcall::cluster_state::{Self,State, ReceiptKey,get_state,get_state_mut}; + use std::string::{Self, String}; + use sui::bag::{Bag, Self}; + use sui::event; + use sui::table::{Self, Table}; + use sui::sui::SUI; + use sui::coin::{Self, Coin}; + use sui::balance; + use xcall::xcall_utils::{Self as utils}; + use xcall::xcall_state::{Self,ConnCap}; + use xcall::xcall_state::{Storage as XCallState}; + + const ENotEnoughFee: u64 = 10; + /* ================= events ================= */ + + public struct Message has copy, drop { + to:String, + conn_sn:u128, + msg:vector, + // same package can instantiate multiple connection so this is required + connection_id:String, + } + + public(package) fun connect():State{ + cluster_state::create() + } + + public fun get_fee(states:&Bag,connection_id:String,netId:String,response:bool):u64{ + let state= get_state(states,connection_id); + cluster_state::get_fee(state,&netId,response) + } + + public(package) fun get_next_connection_sn(state:&mut State):u128 { + let sn = cluster_state::get_next_conn_sn(state); + sn + } + // this is safe because only package can call this other xcall will call other deployed instance + public(package) fun send_message(states:&mut Bag,connection_id:String,coin:Coin,to:String,sn:u128,msg:vector,is_response:bool,ctx: &mut TxContext){ + let mut fee = 0; + if(!is_response){ + fee = get_fee(states,connection_id, to, sn>0); + }; + assert!(coin.value() >= fee, ENotEnoughFee); + let balance = coin.into_balance(); + cluster_state::deposit(get_state_mut(states,connection_id),balance); + let conn_sn = get_next_connection_sn(get_state_mut(states,connection_id)); + + event::emit(Message { to, conn_sn, msg, connection_id }); + } + +} + diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move new file mode 100644 index 00000000..0bbf2fdc --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move @@ -0,0 +1,65 @@ +module xcall::cluster_entry{ + + use xcall::main::{Self as xcall}; + use xcall::xcall_state::{Self,Storage as XCallState,ConnCap}; + use xcall::cluster_state::{Self,get_state,get_state_mut,validate_admin_cap, Validator, AdminCap}; + use xcall::xcall_utils::{Self as utils}; + use std::string::{String}; + + entry public fun receive_message(xcall:&mut XCallState,cap:&ConnCap,src_net_id:String,sn:u128,msg:vector,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::check_save_receipt(state, src_net_id, sn); + xcall::handle_message(xcall, cap,src_net_id, msg,ctx); + } + + + entry fun claim_fees(xcall:&mut XCallState,cap:&ConnCap,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::claim_fees(state,ctx); + } + + entry fun set_fee(xcall:&mut XCallState,cap:&ConnCap,net_id:String,message_fee:u64,response_fee:u64, ctx: &TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::set_fee(state,net_id,message_fee,response_fee,ctx.sender()); + } + + entry fun get_receipt(states: &XCallState,connection_id:String,net_id:String,sn:u128,_ctx: &TxContext):bool{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_receipt(state,net_id,sn) + } + + entry fun get_fee(states: &XCallState,connection_id:String,net_id:String,response:bool,_ctx: &TxContext):u64{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_fee(state,&net_id,response) + } + + entry fun set_validators(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,validator_pubkey:vector,threshold:u64,_ctx: &mut TxContext){ + validate_admin_cap(cap,connection_id); + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),connection_id); + cluster_state::set_validators(state,validator_pubkey,threshold); + } + + entry fun set_validator_threshold(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,threshold:u64,_ctx: &mut TxContext){ + validate_admin_cap(cap,connection_id); + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),connection_id); + cluster_state::set_validator_threshold(state,threshold); + } + + entry fun get_validators(states: &XCallState,connection_id:String,_ctx: &TxContext):vector{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_validators(state) + } + + entry fun get_validators_threshold(states: &XCallState,connection_id:String,_ctx: &TxContext):u64{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_validator_threshold(state) + } + + entry fun recieve_message_with_signatures(xcall:&mut XCallState,cap:&ConnCap,src_net_id:String,sn:u128,msg:vector,signatures:vector>,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + let message_hash = utils::get_message_hash(src_net_id, sn, msg); + cluster_state::verify_signatures(state, message_hash, signatures); + cluster_state::check_save_receipt(state, src_net_id, sn); + xcall::handle_message(xcall, cap,src_net_id, msg,ctx); + } +} \ No newline at end of file diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_state.move b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move new file mode 100644 index 00000000..5a0b7dcb --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move @@ -0,0 +1,340 @@ +module xcall::cluster_state { + use std::string::{String}; + use sui::vec_map::{Self, VecMap}; + use xcall::xcall_utils::{Self as utils}; + use xcall::signatures::{pubkey_to_sui_address, verify_signature, get_pubkey_from_signature}; + use sui::coin::{Self}; + use sui::balance::{Self, Balance}; + use sui::sui::SUI; + use sui::bag::{Bag, Self}; + use sui::event; + + //ERRORS + const VerifiedSignaturesLessThanThreshold: u64 = 100; + const NotEnoughSignatures: u64 = 101; + const InvalidThreshold: u64 = 102; + const ValidatorCountMustBeGreaterThanThreshold: u64 = 105; + const InvalidAdminCap: u64 = 106; + + //EVENTS + public struct ValidatorSetAdded has copy, drop { + validators: vector, + threshold: u64 + } + + public struct AdminCap has key,store { + id: UID, + connection_id: String + } + + public(package) fun create_admin_cap(connection_id:String,ctx: &mut TxContext):AdminCap { + let admin = AdminCap { + id: object::new(ctx), + connection_id: connection_id + }; + admin + } + + public(package) fun validate_admin_cap(self:&AdminCap,connection_id:String){ + assert!(self.connection_id == connection_id, InvalidAdminCap); + } + + public(package) fun get_state_mut(states:&mut Bag,connection_id:String):&mut State { + let state:&mut State=bag::borrow_mut(states,connection_id); + state + } + + public fun get_state(states:&Bag,connection_id:String):&State { + let state:&State=bag::borrow(states,connection_id); + state + } + + + public struct ReceiptKey has copy, drop, store { + conn_sn: u128, + nid: String, + } + + public struct Validator has store, drop, copy{ + pub_key:vector, + sui_address:address, + } + + public struct State has store{ + message_fee: VecMap, + response_fee: VecMap, + receipts: VecMap, + conn_sn: u128, + balance: Balance, + validators: vector, + validators_threshold:u64, + + } + + public(package) fun create(): State { + State { + message_fee: vec_map::empty(), + response_fee: vec_map::empty(), + conn_sn: 0, + receipts: vec_map::empty(), + balance:balance::zero(), + validators: vector::empty(), + validators_threshold:0 + } + } + + fun get_validator(pub_key:String):Validator{ + let (pubkey,sui_address)=pubkey_to_sui_address(&pub_key); + Validator{ + pub_key:pubkey, + sui_address:sui_address + } + } + + public(package) fun get_next_conn_sn(self:&mut State):u128 { + let sn=self.conn_sn+1; + self.conn_sn=sn; + sn + } + + public fun get_fee(self: &State, netId: &String, need_response: bool): u64 { + let fee: u64 = if (need_response == true) { + utils::get_or_default(&self.message_fee, netId, 0) + + utils::get_or_default(&self.response_fee, netId, 0) + } else { + utils::get_or_default(&self.message_fee, netId, 0) + }; + fee + } + + public(package) fun set_fee(self: &mut State, net_id: String, message_fee: u64, response_fee: u64,caller:address) { + if (vec_map::contains(&self.message_fee,&net_id)){ + vec_map::remove(&mut self.message_fee,&net_id); + }; + if (vec_map::contains(&self.response_fee,&net_id)){ + vec_map::remove(&mut self.response_fee,&net_id); + }; + vec_map::insert(&mut self.message_fee, net_id, message_fee); + vec_map::insert(&mut self.response_fee, net_id, response_fee); + } + + public(package) fun check_save_receipt(self: &mut State, net_id: String, sn: u128) { + let receipt_key = ReceiptKey { nid: net_id, conn_sn: sn }; + assert!(!vec_map::contains(&self.receipts, &receipt_key), 100); + vec_map::insert(&mut self.receipts, receipt_key, true); + } + + public(package) fun get_receipt(self: &State, net_id: String, sn: u128): bool { + let receipt_key = ReceiptKey { nid: net_id, conn_sn: sn }; + vec_map::contains(&self.receipts, &receipt_key) + } + + public(package) fun deposit(self:&mut State,balance:Balance){ + balance::join(&mut self.balance,balance); + + } + + public(package) fun claim_fees(self:&mut State,ctx:&mut TxContext){ + let total= self.balance.withdraw_all(); + let coin= coin::from_balance(total,ctx); + transfer::public_transfer(coin,ctx.sender()); + + } + + public(package) fun verify_signatures(self:&State,message_hash:vector,signatures:vector>){ + let threshold=self.get_validator_threshold(); + let validators=self.get_validators().map!(|validator| validator.pub_key); + assert!(signatures.length() >= threshold, NotEnoughSignatures); + let mut i = 0; + let mut unique_verified_pubkey = vector::empty(); + while (i < signatures.length()) { + let signature = signatures.borrow(i); + let pub_key = get_pubkey_from_signature(signature); + if (validators.contains(&pub_key)) { + + if (verify_signature(&pub_key,signature,&message_hash)) { + + if (!unique_verified_pubkey.contains(&pub_key)){ + unique_verified_pubkey.push_back(pub_key); + }; + + if (unique_verified_pubkey.length() >= threshold) { + return + }; + }; + }; + i=i+1; + }; + assert!(unique_verified_pubkey.length() >= threshold, VerifiedSignaturesLessThanThreshold); + } + + public(package) fun get_validator_threshold(self:&State):u64{ + self.validators_threshold + } + + public(package) fun set_validator_threshold(self:&mut State,threshold:u64){ + assert!(threshold <= self.validators.length(), InvalidThreshold); + self.validators_threshold=threshold + } + + public(package) fun set_validators(self:&mut State,validator_pub_keys:vector,threshold:u64){ + self.validators=vector::empty(); + let mut validator_pub_keys = validator_pub_keys; + while (validator_pub_keys.length() > 0) { + let validator_pub_key = validator_pub_keys.pop_back(); + let validator=get_validator(validator_pub_key); + if(self.validators.contains(&validator)){ + continue + }; + self.validators.push_back(validator); + }; + assert!(self.validators.length() >= threshold, ValidatorCountMustBeGreaterThanThreshold); + self.validators_threshold=threshold; + event::emit(ValidatorSetAdded { validators: self.validators, threshold }); + } + + public(package) fun get_validators(self:&State):vector{ + self.validators + } + + #[test_only] + public(package) fun create_state():State{ + State { + message_fee: vec_map::empty(), + response_fee: vec_map::empty(), + conn_sn: 0, + receipts: vec_map::empty(), + balance:balance::zero(), + validators: vector::empty(), + validators_threshold:0 + } + } + +} + +#[test_only] +module xcall::cluster_state_tests { + use xcall::cluster_state::{State, get_validators, get_validator_threshold, set_validator_threshold, set_validators, verify_signatures}; + use sui::test_scenario::{Self, Scenario}; + + #[test] + fun test_add_validator(): State { + let mut state = xcall::cluster_state::create_state(); + + let validators = vector[ + b"AJ6snNNaDhPZLg06AkcvYL0TZe4+JgoWtZKG/EJmzdWi".to_string(), + b"ADDxHCpQUcFsy5H5Gy01uv7LoISvtJLfgVGfWy4bLrjO".to_string(), + b"AL0hUNIiz5Q2fv0siZc75ce3aOyUpiiI+Q8Rmfay4K/X".to_string(), + b"ALnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA".to_string() + ]; + + set_validators(&mut state, validators, 2); + + let validators = get_validators(&state); + assert!((validators.length() == 4)); + + + set_validator_threshold(&mut state, 3); + assert!(get_validator_threshold(&state)==3); + + state + } + + + #[test] + fun test_set_get_threshold(): State { + let mut state = test_add_validator(); + set_validator_threshold(&mut state, 2); + assert!(get_validator_threshold(&state)==2); + state + } + + #[test] + #[expected_failure(abort_code = 102)] + fun test_set_threshold_too_high(): State { + let mut state = test_set_get_threshold(); + set_validator_threshold(&mut state, 5); + state + } + + #[test] + fun test_get_fee(): State { + let mut state = xcall::cluster_state::create_state(); + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 100, 50, @0xadd); + xcall::cluster_state::set_fee(&mut state, b"net2".to_string(), 200, 100, @0xadd); + + let fee_without_response = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), false); + assert!(fee_without_response == 100); + + let fee_with_response = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(fee_with_response == 150); + + state + } + + #[test] + fun test_update_fee(): State { + let mut state = xcall::cluster_state::create_state(); + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 200, 100, @0xadd); + + let fee = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(fee == 300); // 200 message_fee + 100 response_fee + + // Update the fee + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 300, 200, @0xadd); + let updated_fee = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(updated_fee == 500); // 300 message_fee + 200 response_fee + + state + } + + #[test] + fun test_receipts(): State { + let mut state = xcall::cluster_state::create_state(); + let sn = xcall::cluster_state::get_next_conn_sn(&mut state); + + xcall::cluster_state::check_save_receipt(&mut state, b"net1".to_string(), sn); + let receipt_exists = xcall::cluster_state::get_receipt(&state, b"net1".to_string(), sn); + assert!(receipt_exists == true); + + state + } + + #[test] + #[expected_failure(abort_code = 101)] + fun test_verify_signatures_less_than_threshold(): State { + let state = test_set_get_threshold(); + + let msg: vector = x"6162636465666768"; + let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200"]; + + xcall::cluster_state::verify_signatures(&state, msg, signatures); + state + } + + #[test] + #[expected_failure(abort_code = 100)] + fun test_verify_signatures_invalid(): State { + let state = test_set_get_threshold(); + let msg: vector = x"6162636465666768"; + let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200", + x"00c6d94cc625e73e036852316d228e578893aad2a7b21febc08f92a5d53978154782e4a0551ced23fb92c765b4cb4715e231de0235e2b641b81a36b9f2a3f8630d9eac9cd35a0e13d92e0d3a02472f60bd1365ee3e260a16b59286fc4266cdd5a2" + ]; + + xcall::cluster_state::verify_signatures(&state, msg, signatures); + state + } + + #[test] + fun test_verify_signatures(): State { + let state = test_set_get_threshold(); + let msg: vector = x"6162636465666768"; + let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200", + x"00c6d94cc625e73e036852316d229e578893aad2a7b21febc08f92a5d53978154782e4a0551ced23fb92c765b4cb4715e231de0235e2b641b81a36b9f2a3f8630d9eac9cd35a0e13d92e0d3a02472f60bd1365ee3e260a16b59286fc4266cdd5a2" + ]; + + xcall::cluster_state::verify_signatures(&state, msg, signatures); + state + } + +} \ No newline at end of file diff --git a/contracts/sui/xcall/sources/cluster_connection/signatures.move b/contracts/sui/xcall/sources/cluster_connection/signatures.move new file mode 100644 index 00000000..81fe18ad --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/signatures.move @@ -0,0 +1,196 @@ +#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] + +module xcall::signatures { + use sui::linked_table::{Self, LinkedTable}; + use sui::types as sui_types; + use std::string::{Self, String}; + use sui::event; + use sui::hash::{Self}; + use sui::vec_map::{Self, VecMap}; + use sui::table::{Table,Self}; + use sui::bcs::{Self}; + use sui::address::{Self}; + use sui::{ed25519::ed25519_verify}; + use sui::{ecdsa_k1::secp256k1_verify}; + use sui::{ecdsa_r1::secp256r1_verify}; + + const BASE64_CHARS: vector = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const PADDING_CHAR: vector = b"="; + + /** signature schemes*/ + const FlagED25519 :u8= 0x00; + const FlagSecp256k1 :u8= 0x01; + const FlagSecp256r1 :u8= 0x02; + const FlagMultiSig :u8= 0x03; + + /* hash algorithm*/ + const KECCAK256: u8 = 0x00; + const SHA256: u8 = 0x01; + + public fun decode(input:&String):vector{ + let char_index=get_char_map(); + let mut output = vector::empty(); + let input_bytes = input.as_bytes(); + let mut i = 0; + while( i < input_bytes.length()){ + let b1 = *char_index.get(input_bytes.borrow(i)); + let b2 = *char_index.get(input_bytes.borrow(i + 1)); + let b3 = if (i + 2 < input_bytes.length()) { + let key=input_bytes.borrow(i+2); + let val:u32 = if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + 64 + }; + let b4 = if (i + 3 < input_bytes.length()) { + let key=input_bytes.borrow(i+3); + let val:u32= if (char_index.contains(key)) { + *char_index.get(key) + } else { + 64 + }; + val + } else { + 64 + }; + let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; + output.push_back(((triple >> 16) & 0xFF) as u8); + if (b3 != 64) { + output.push_back(((triple >> 8) & 0xFF) as u8); + }; + if (b4 != 64) { + output.push_back((triple & 0xFF) as u8); + }; + i = i+4; + }; + output + } + + fun get_char_map():VecMap{ + let mut char_map = vec_map::empty(); + let mut i:u64=0; + while( i < BASE64_CHARS.length()){ + let c=*BASE64_CHARS.borrow(i); + char_map.insert(c,(i as u32)); + i=i+1; + }; + char_map + } + + public(package) fun pubkey_to_sui_address(pubkey:&String):(vector,address){ + let pubkey_bytes = decode(pubkey); + let sui_address = address::from_bytes(hash::blake2b256(&pubkey_bytes)); + (pubkey_bytes,sui_address) + } + + public(package) fun get_pubkey_from_signature(raw_signature:&vector):vector{ + let (signature,mut pubkey,scheme)= split_signature(raw_signature); + pubkey.insert(scheme, 0); + pubkey + } + + public(package) fun verify_signature(pubkey:&vector,raw_signature:&vector,msg:&vector):bool{ + let flag=*pubkey.borrow(0); + let public_key=slice_vector(pubkey,1,pubkey.length()-1); + let (signature,pub,_scheme)= split_signature(raw_signature); + + let intent_msg= get_intent_message(msg); + let digest= hash::blake2b256(&intent_msg); + + let verify= if(flag==FlagED25519){ + + ed25519_verify(&signature,&public_key,&digest) + + }else if(flag==FlagSecp256k1){ + + secp256k1_verify(&signature,&public_key,msg,SHA256) + + }else if (flag==FlagSecp256r1){ + + secp256r1_verify(&signature,&public_key,msg,SHA256) + }else { + return false + }; + verify + } + + fun get_intent_message(msg:&vector):vector{ + let mut intent_message:vector =vector::empty(); + intent_message.push_back(0x00); + intent_message.push_back(0x00); + intent_message.push_back(0x00); + intent_message.append(*msg); + intent_message + } + + fun split_signature(raw_signature:&vector):(vector,vector,u8){ + let scheme=*raw_signature.borrow(0); + let length= if (scheme==FlagED25519){32}else{33}; + let signature=slice_vector(raw_signature,1,64); + let pubkey=slice_vector(raw_signature,raw_signature.length()-length,length); + return (signature,pubkey,scheme) + } + + public fun slice_vector(vec: &vector, start: u64, length: u64): vector { + let mut result = vector::empty(); + let mut i = 0; + while (i < length) { + let value = *vector::borrow(vec, start + i); + vector::push_back(&mut result, value); + i = i + 1; + }; + result + } + +} + +#[test_only] +module xcall::signature_tests { + use sui::hash::{Self}; + use xcall::signatures::{decode, pubkey_to_sui_address, get_pubkey_from_signature, verify_signature}; + use std::debug::print; + use sui::{ed25519::ed25519_verify}; + + #[test] + fun test_base64(){ + let input = b"SGVsbG8sIFdvcmxkIQ==".to_string(); + let decoded = decode(&input); + assert!(decoded==b"Hello, World!",0x01); + } + + #[test] + fun test_pubkey_to_sui_address(){ + let input = b"AL0hUNIiz5Q2fv0siZc75ce3aOyUpiiI+Q8Rmfay4K/X".to_string(); + let (_pubkey_bytes,sui_address) = pubkey_to_sui_address(&input); + assert!(sui_address==@0xdefd91e5bfdb22edc6cabe1e9490549377047ec476354241910685876f1af34a,0x01); + } + + #[test] + fun test_get_pubkey_from_signature(){ + let input = b"ADQTOEZPWpPeZc9auXhwyciH7Pw6ny8xSxR+JVnSBnNktKAgTgxJ2EwHcErw55enK4w6c1uYaUv2gfY8Vb3X+Q6UHzNBcHIzSfMdKerK6XOCCmHbsI9Tpa8lGFvkm+Gbwg==".to_string(); + let pubkey = get_pubkey_from_signature(&decode(&input)); + assert!(pubkey==decode(&b"AJQfM0FwcjNJ8x0p6srpc4IKYduwj1OlryUYW+Sb4ZvC".to_string()),0x01); + } + + #[test] + fun test_verify_signature(){ + let data=x"6162636465666768"; + let pubkey = b"ALnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA".to_string(); + let signature = b"ALsKe6SiQqSYjIILlKjfmzEunnz0+DArU+4uBG522obq6cFSlqQhuN3bKcr6jVBSPgsEMAIW45PUXAc5oOq45gy5xu4WMO8+cRFEpkjbBruyKE9ydM++5T/87lA8waSSAA==".to_string(); + let verify = verify_signature(&decode(&pubkey),&decode(&signature),&data); + assert!(verify,0x01); + + let data=x"6162636465666768"; + let pubkey = x"b9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200"; + let signature = x"c2ac48f07c55a8da3a0a762032a4956b08c63eebbe3f7a3f4cc56cd7fa23b1a3f7dec39e8e112162797d7c98f87d07314abff3cf594e42c27e942d3af43dcc07"; + + + let verify = ed25519_verify(&signature, &pubkey, &data); + assert!(verify,0x01); + + } +} diff --git a/contracts/sui/xcall/sources/connections.move b/contracts/sui/xcall/sources/connections.move index 170e2d0f..044ffddc 100644 --- a/contracts/sui/xcall/sources/connections.move +++ b/contracts/sui/xcall/sources/connections.move @@ -1,24 +1,33 @@ -#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] +#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment,implicit_const_copy)] module xcall::connections{ use std::string::{Self, String}; use sui::bag::{Bag, Self}; use xcall::centralized_connection::{Self}; - use xcall::centralized_state::{Self,State}; + use xcall::cluster_connection::{Self}; + use xcall::cluster_state::{Self,State,create_admin_cap}; use xcall::xcall_state::{ConnCap}; use sui::coin::{Self,Coin}; use sui::balance::{Self, Balance}; use sui::sui::SUI; + const EConnectionNotFound:u64=0; const ConnCentralized:vector =b"centralized"; + const ConnCluster:vector =b"cluster"; public(package) fun register(states:&mut Bag,connection_id:String,ctx:&mut TxContext){ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ let state= centralized_connection::connect(); bag::add(states, connection_id, state); + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + let state= cluster_connection::connect(); + let admin_cap=cluster_state::create_admin_cap(connection_id,ctx); + transfer::public_transfer(admin_cap, ctx.sender()); + bag::add(states, connection_id, state); }else{ abort EConnectionNotFound } @@ -27,26 +36,33 @@ module xcall::connections{ public(package) fun get_fee(states:&Bag,connection_id:String,netId:String,response:bool):u64{ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ let fee= centralized_connection::get_fee(states,connection_id,netId,response); fee + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + let fee= cluster_connection::get_fee(states,connection_id,netId,response); + fee }else{ abort EConnectionNotFound - } + } } public(package) fun send_message(states:&mut Bag,connection_id:String,coin:Coin,netId:String,sn:u128,msg:vector,is_response:bool,ctx:&mut TxContext){ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ centralized_connection::send_message(states,connection_id,coin,netId,sn,msg,is_response,ctx); + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + cluster_connection::send_message(states,connection_id,coin,netId,sn,msg,is_response,ctx); }else{ abort EConnectionNotFound - } + } } fun get_connection_type(connection_id:&String):String{ let separator_index=string::index_of(connection_id,&string::utf8(b"-")); - let connType=string::sub_string(connection_id,0,separator_index); + let connType=string::substring(connection_id,0,separator_index); connType } diff --git a/contracts/sui/xcall/sources/utils.move b/contracts/sui/xcall/sources/utils.move index 66e0ff9d..889691b1 100644 --- a/contracts/sui/xcall/sources/utils.move +++ b/contracts/sui/xcall/sources/utils.move @@ -8,7 +8,9 @@ module xcall::xcall_utils { use std::string::{Self, String}; use sui::hex; use sui::bcs::{Self}; - + use sui_rlp::encoder::{Self}; + use sui::hash::{Self}; + public fun are_equal(a1:&vector,a2:&vector): bool { if(length(a1)!=length(a2)){ @@ -24,6 +26,15 @@ module xcall::xcall_utils { } } + public fun get_message_hash(src_net_id: String, sn: u128, msg: vector): vector { + let mut list=vector::empty>(); + vector::push_back(&mut list, encoder::encode_string(&src_net_id)); + vector::push_back(&mut list, encoder::encode_u128(sn)); + vector::push_back(&mut list, encoder::encode(&msg)); + let encoded=encoder::encode_list(&list,false); + hash::keccak256(&encoded) + + } public fun id_to_hex_string(id:&ID): String { let bytes = object::id_to_bytes(id); From d9f95c9b7a5f0545204c2d08681434ec1a2b3007 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Wed, 6 Nov 2024 13:19:06 +0545 Subject: [PATCH 53/75] fix: rlp zero values --- .../sui/libs/sui_rlp/sources/encoder.move | 25 +++++++++++++++++++ contracts/sui/libs/sui_rlp/sources/utils.move | 7 ++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index ea0dadff..980db768 100644 --- a/contracts/sui/libs/sui_rlp/sources/encoder.move +++ b/contracts/sui/libs/sui_rlp/sources/encoder.move @@ -83,6 +83,12 @@ module sui_rlp::encoder { let vec=vector::singleton(num); encode(&vec) + } + + public fun encode_u32(num:u32):vector{ + let vec= utils::to_bytes_u32(num); + encode(&vec) + } public fun encode_u64(num:u64):vector{ @@ -126,5 +132,24 @@ module sui_rlp::encoder { vector[0] } + #[test] + fun test_encode_zero_value(){ + let num=0_u128; + let bytes=encode_u128(num); + assert!(bytes==x"00"); + + let num=0_u64; + let bytes=encode_u64(num); + assert!(bytes==x"00"); + + let num=0_u32; + let bytes=encode_u32(num); + assert!(bytes==x"00"); + + let num=0_u8; + let bytes=encode_u8(num); + assert!(bytes==x"00"); + } + } \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/utils.move b/contracts/sui/libs/sui_rlp/sources/utils.move index e09d9d88..f1c76eb8 100644 --- a/contracts/sui/libs/sui_rlp/sources/utils.move +++ b/contracts/sui/libs/sui_rlp/sources/utils.move @@ -86,8 +86,11 @@ module sui_rlp::utils { i = i + 1; }; - - result + if (result.length()==0){ + vector[0] + }else{ + result + } } From 13157fef8e78faad51415ada3fe93c78d275a617 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Fri, 8 Nov 2024 15:54:21 +0545 Subject: [PATCH 54/75] fix: updated new func signature to recv pubkey byte array --- .../adapter/cluster/ClusterConnection.java | 56 ++++++++++++------- .../cluster/ClusterConnectionTest.java | 45 +++++++++++++-- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 9c358cf2..0c2b026e 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -30,6 +30,8 @@ import score.annotation.EventLog; import score.annotation.External; import score.annotation.Payable; + +import java.util.Arrays; import java.util.List; @@ -40,7 +42,7 @@ public class ClusterConnection { protected final VarDB
relayerAddress = Context.newVarDB("relayer", Address.class); protected final VarDB validatorsThreshold = Context.newVarDB("reqValidatorCnt", BigInteger.class); private final VarDB connSn = Context.newVarDB("connSn", BigInteger.class); - private final ArrayDB
validators = Context.newArrayDB("signers", Address.class); + private final ArrayDB validators = Context.newArrayDB("signers", String.class); protected final DictDB messageFees = Context.newDictDB("messageFees", BigInteger.class); protected final DictDB responseFees = Context.newDictDB("responseFees", BigInteger.class); @@ -61,8 +63,8 @@ public ClusterConnection(Address _relayer, Address _xCall) { * @return The validators . */ @External(readonly = true) - public Address[] listValidators() { - Address[] sgs = new Address[validators.size()]; + public String[] listValidators() { + String[] sgs = new String[validators.size()]; for(int i = 0; i < validators.size(); i++) { sgs[i] = validators.get(i); } @@ -76,17 +78,18 @@ public Address[] listValidators() { * Ensures that the caller is an admin and that the number of validators * meets or exceeds the specified threshold. * - * @param _validators an array of addresses to be added as validators + * @param _validators an array of compressed publickey bytes to be added as validators * @param _threshold the minimum required number of validators * @throws Exception if the number of validators is less than the threshold */ @External - public void updateValidators(Address[] _validators, BigInteger _threshold) { + public void updateValidators(byte[][] _validators, BigInteger _threshold) { OnlyAdmin(); clearValidators(); - for (Address validator : _validators) { - if(!isValidator(validator)) { - validators.add(validator); + for (byte[] validator : _validators) { + String hexValidator = bytesToHex(validator); + if(!isValidator(hexValidator)) { + validators.add(bytesToHex(validator)); } } Context.require(validators.size() >= _threshold.intValue(), "Not enough validators"); @@ -106,13 +109,12 @@ private void clearValidators() { } /** - * Checks if the provided address is a validator. + * Checks if the provided compressed pubkey bytes is a validator. * - * @param validator the address to check for validation - * @return true if the address is a validator, false otherwise + * @param validator the compressed publickey bytes to check for validation + * @return true if the compressed pubkey bytes is a validator, false otherwise */ - @External(readonly = true) - public boolean isValidator(Address validator) { + private boolean isValidator(String validator) { for(int i = 0; i < validators.size(); i++) { if(validator.equals(validators.get(i))) { return true; @@ -254,12 +256,13 @@ public void recvMessageWithSignatures(String srcNetwork, BigInteger _connSn, byt OnlyRelayer(); Context.require(signatures.length >= validatorsThreshold.get().intValue(), "Not enough signatures"); byte[] messageHash = getMessageHash(srcNetwork, _connSn, msg); - List
uniqueValidators = new ArrayList<>(); + List uniqueValidators = new ArrayList<>(); for (byte[] signature : signatures) { - Address validator = getValidator(messageHash, signature); - Context.require(isValidator(validator), "Invalid signature provided"); - if (!uniqueValidators.contains(validator)) { - uniqueValidators.add(validator); + byte[] validator = getValidator(messageHash, signature); + String hexValidator = bytesToHex(validator); + Context.require(isValidator(hexValidator), "Invalid signature provided"); + if (!uniqueValidators.contains(hexValidator)) { + uniqueValidators.add(hexValidator); } } Context.require(uniqueValidators.size() >= validatorsThreshold.get().intValue(), "Not enough valid signatures"); @@ -272,9 +275,20 @@ private void recvMessage(String srcNetwork, BigInteger _connSn, byte[] msg) { Context.call(xCall.get(), "handleMessage", srcNetwork, msg); } - private Address getValidator(byte[] msg, byte[] sig){ - byte[] key = Context.recoverKey("ecdsa-secp256k1", msg, sig, true); - return Context.getAddressFromKey(key); + private String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); // Mask with 0xff to handle negative values correctly + if (hex.length() == 1) { + hexString.append('0'); // Add a leading zero if hex length is 1 + } + hexString.append(hex); + } + return hexString.toString(); + } + + private byte[] getValidator(byte[] msg, byte[] sig){ + return Context.recoverKey("ecdsa-secp256k1", msg, sig, true); } /** diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index f165323a..15d2ef7f 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -5,9 +5,12 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import java.nio.charset.StandardCharsets; import java.security.*; import java.math.BigInteger; +import java.util.Arrays; + import score.Context; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -163,12 +166,26 @@ public void testRecvMessageWithSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } + private byte[] compressPublicKey(byte[] uncompressedPublicKey) { + byte[] xBytes = Arrays.copyOfRange(uncompressedPublicKey, 1, 33); // 32 bytes for x + byte[] yBytes = Arrays.copyOfRange(uncompressedPublicKey, 33, 65); // 32 bytes for y + BigInteger y = new BigInteger(1, yBytes); + byte prefix = (y.testBit(0)) ? (byte) 0x03 : (byte) 0x02; + byte[] compressedKey = new byte[33]; + compressedKey[0] = prefix; + System.arraycopy(xBytes, 0, compressedKey, 1, 32); + return compressedKey; + + } + @Test public void testRecvMessageWithMultiSignatures() throws Exception{ byte[] data = "test".getBytes(); @@ -178,7 +195,11 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(wallet2.getAddress().toString())}; + byte[] compressPublicKey = compressPublicKey(wallet.getPublicKey().toByteArray()); + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()) + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); @@ -189,9 +210,13 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[] data = "test".getBytes(); byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); @@ -204,10 +229,14 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byte[] data = "test".getBytes(); byte[] messageHash = getMessageHash(nidSource, BigInteger.ONE, data); KeyWallet wallet = KeyWallet.create(); + KeyWallet wallet2 = KeyWallet.create(); byte[][] byteArray = new byte[2][]; byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); - Address[] validators = new Address[] {Address.fromString(wallet.getAddress().toString()), Address.fromString(owner.getAddress().toString())}; + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, ()->connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray)); @@ -228,9 +257,13 @@ public static byte[] getMessageHash(String srcNetwork, BigInteger _connSn, byte[ @Test public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); - Address[] validators = new Address[] {Address.fromString(owner.getAddress().toString()), Address.fromString(wallet.getAddress().toString())}; + KeyWallet wallet2 = KeyWallet.create(); + byte[][] validators = new byte[][] { + compressPublicKey(wallet.getPublicKey().toByteArray()), + compressPublicKey(wallet2.getPublicKey().toByteArray()), + }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); - Address[] signers = connection.call(Address[].class,"listValidators"); + String[] signers = connection.call(String[].class,"listValidators"); assertEquals(signers.length, 2); } From d7f11de37e2b4fbfc8f80c9e6b0ff10cc9f74e15 Mon Sep 17 00:00:00 2001 From: "Biru C. Sainju" Date: Mon, 11 Nov 2024 10:23:24 +0545 Subject: [PATCH 55/75] fix: used uncompressed pubkey --- .../adapter/cluster/ClusterConnection.java | 2 +- .../cluster/ClusterConnectionTest.java | 30 ++++++------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java index 0c2b026e..6546187f 100644 --- a/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java +++ b/contracts/javascore/cluster-connection/src/main/java/xcall/adapter/cluster/ClusterConnection.java @@ -288,7 +288,7 @@ private String bytesToHex(byte[] bytes) { } private byte[] getValidator(byte[] msg, byte[] sig){ - return Context.recoverKey("ecdsa-secp256k1", msg, sig, true); + return Context.recoverKey("ecdsa-secp256k1", msg, sig, false); } /** diff --git a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java index 15d2ef7f..c856f5a7 100644 --- a/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java +++ b/contracts/javascore/cluster-connection/src/test/java/xcall/adapter/cluster/ClusterConnectionTest.java @@ -167,24 +167,13 @@ public void testRecvMessageWithSignatures() throws Exception{ KeyWallet wallet = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.ONE); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); verify(callservice.mock).handleMessage(eq(nidSource), eq("test".getBytes())); } - private byte[] compressPublicKey(byte[] uncompressedPublicKey) { - byte[] xBytes = Arrays.copyOfRange(uncompressedPublicKey, 1, 33); // 32 bytes for x - byte[] yBytes = Arrays.copyOfRange(uncompressedPublicKey, 33, 65); // 32 bytes for y - BigInteger y = new BigInteger(1, yBytes); - byte prefix = (y.testBit(0)) ? (byte) 0x03 : (byte) 0x02; - byte[] compressedKey = new byte[33]; - compressedKey[0] = prefix; - System.arraycopy(xBytes, 0, compressedKey, 1, 32); - return compressedKey; - - } @Test public void testRecvMessageWithMultiSignatures() throws Exception{ @@ -195,10 +184,9 @@ public void testRecvMessageWithMultiSignatures() throws Exception{ KeyWallet wallet2 = KeyWallet.create(); byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet2.sign(messageHash); - byte[] compressPublicKey = compressPublicKey(wallet.getPublicKey().toByteArray()); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()) + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); connection.invoke(source_relayer, "recvMessageWithSignatures", nidSource, BigInteger.ONE, data, byteArray); @@ -214,8 +202,8 @@ public void testRecvMessageWithSignaturesNotEnoughSignatures() throws Exception{ byte[][] byteArray = new byte[1][]; byteArray[0] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, @@ -234,8 +222,8 @@ public void testRecvMessageWithSignaturesNotEnoughValidSignatures() throws Excep byteArray[0] = wallet.sign(messageHash); byteArray[1] = wallet.sign(messageHash); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); UserRevertedException e = assertThrows(UserRevertedException.class, @@ -259,8 +247,8 @@ public void testAddSigners() throws Exception{ KeyWallet wallet = KeyWallet.create(); KeyWallet wallet2 = KeyWallet.create(); byte[][] validators = new byte[][] { - compressPublicKey(wallet.getPublicKey().toByteArray()), - compressPublicKey(wallet2.getPublicKey().toByteArray()), + wallet.getPublicKey().toByteArray(), + wallet2.getPublicKey().toByteArray(), }; connection.invoke(owner, "updateValidators", validators, BigInteger.TWO); String[] signers = connection.call(String[].class,"listValidators"); From 7013fcaf10572c641f482b5de7a419cfcbf4d463 Mon Sep 17 00:00:00 2001 From: ibrizsabin Date: Thu, 14 Nov 2024 11:23:52 +0545 Subject: [PATCH 56/75] fix: remove sign byte from length --- Cargo.lock | 971 +----------------- .../sui/libs/sui_rlp/sources/encoder.move | 8 +- contracts/sui/libs/sui_rlp/sources/utils.move | 23 +- 3 files changed, 29 insertions(+), 973 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36dc3585..3c98cba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.7.6" @@ -34,30 +19,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arrayvec" version = "0.7.4" @@ -70,21 +37,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -97,12 +49,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" version = "0.11.0" @@ -121,12 +67,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.6.0" @@ -214,12 +154,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -241,31 +175,12 @@ dependencies = [ "serde", ] -[[package]] -name = "bytes-lit" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" -dependencies = [ - "num-bigint", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -[[package]] -name = "centralized-connection" -version = "0.0.0" -dependencies = [ - "soroban-sdk", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -279,10 +194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", - "iana-time-zone", "num-traits", - "serde", - "winapi", ] [[package]] @@ -299,7 +211,7 @@ dependencies = [ "displaydoc", "dyn-clone", "hex", - "hex-literal 0.3.4", + "hex-literal", "ibc-proto", "ics23", "pbjson", @@ -325,12 +237,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "cosmwasm" version = "0.7.2" @@ -432,17 +338,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crate-git-revision" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" -dependencies = [ - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -483,16 +378,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" -dependencies = [ - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -506,33 +391,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -684,7 +542,7 @@ dependencies = [ "cw-storage-plus 0.15.1", "cw-utils 0.15.1", "derivative", - "itertools 0.10.5", + "itertools", "prost 0.9.0", "schemars 0.8.12", "serde", @@ -702,7 +560,7 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "derivative", - "itertools 0.10.5", + "itertools", "k256 0.11.6", "prost 0.9.0", "schemars 0.8.12", @@ -930,41 +788,6 @@ dependencies = [ "serde", ] -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.70", - "quote 1.0.33", - "strsim", - "syn 2.0.42", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "debug_print" version = "1.0.0" @@ -1002,17 +825,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1062,12 +874,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - [[package]] name = "dyn-clone" version = "1.0.11" @@ -1109,16 +915,6 @@ dependencies = [ "signature 1.6.4", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", -] - [[package]] name = "ed25519-consensus" version = "2.1.0" @@ -1132,28 +928,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", - "rand_core 0.6.4", - "serde", - "sha2 0.10.8", - "subtle", - "zeroize", -] - [[package]] name = "ed25519-zebra" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek", "hashbrown 0.12.3", "hex", "rand_core 0.6.4", @@ -1243,18 +1024,6 @@ dependencies = [ "libc", ] -[[package]] -name = "escape-bytes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" - -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - [[package]] name = "fastrand" version = "1.9.0" @@ -1284,12 +1053,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "fixed-hash" version = "0.8.0" @@ -1314,12 +1077,6 @@ dependencies = [ "paste", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "forward_ref" version = "1.0.0" @@ -1405,18 +1162,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "group" version = "0.12.1" @@ -1471,9 +1220,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-buffer-serde" @@ -1491,12 +1237,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - [[package]] name = "hmac" version = "0.12.1" @@ -1506,29 +1246,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ibc" version = "0.32.0" @@ -1595,12 +1312,6 @@ dependencies = [ "sha3", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "impl-serde" version = "0.4.0" @@ -1629,7 +1340,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -1640,15 +1350,8 @@ checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.3", - "serde", ] -[[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - [[package]] name = "instant" version = "0.1.12" @@ -1678,30 +1381,12 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.11.6" @@ -1749,12 +1434,6 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1773,41 +1452,12 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mock-dapp-multi" -version = "0.0.0" -dependencies = [ - "soroban-rlp", - "soroban-sdk", - "soroban-xcall-lib", - "xcall", -] - [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1819,26 +1469,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1848,15 +1478,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.18.0" @@ -1869,18 +1490,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", - "sha2 0.10.8", -] - [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -1928,7 +1537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbb7b706f2afc610f3853550cdbbf6372fd324824a087806bd4480ea4996e24" dependencies = [ "heck", - "itertools 0.10.5", + "itertools", "prost 0.11.9", "prost-types", ] @@ -1990,15 +1599,6 @@ dependencies = [ "spki 0.7.3", ] -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" version = "0.1.25" @@ -2009,25 +1609,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "prettyplease" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" -dependencies = [ - "proc-macro2 1.0.70", - "syn 2.0.42", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve 0.13.8", -] - [[package]] name = "primitive-types" version = "0.12.2" @@ -2114,12 +1695,12 @@ checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck", - "itertools 0.10.5", + "itertools", "lazy_static", "log", "multimap", "petgraph", - "prettyplease 0.1.25", + "prettyplease", "prost 0.11.9", "prost-types", "regex", @@ -2135,7 +1716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", @@ -2148,7 +1729,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", @@ -2181,27 +1762,6 @@ dependencies = [ "proc-macro2 1.0.70", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -2282,27 +1842,12 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.37.19" @@ -2572,36 +2117,6 @@ dependencies = [ "syn 2.0.42", ] -[[package]] -name = "serde_with" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.1.0", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" -dependencies = [ - "darling", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "sha2" version = "0.9.9" @@ -2656,12 +2171,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - [[package]] name = "snafu" version = "0.5.0" @@ -2683,210 +2192,6 @@ dependencies = [ "syn 0.15.44", ] -[[package]] -name = "soroban-builtin-sdk-macros" -version = "21.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" -dependencies = [ - "itertools 0.11.0", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - -[[package]] -name = "soroban-env-common" -version = "21.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" -dependencies = [ - "arbitrary", - "crate-git-revision", - "ethnum", - "num-derive 0.4.2", - "num-traits", - "serde", - "soroban-env-macros", - "soroban-wasmi", - "static_assertions", - "stellar-xdr", - "wasmparser", -] - -[[package]] -name = "soroban-env-guest" -version = "21.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" -dependencies = [ - "soroban-env-common", - "static_assertions", -] - -[[package]] -name = "soroban-env-host" -version = "21.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" -dependencies = [ - "backtrace", - "curve25519-dalek 4.1.3", - "ecdsa 0.16.9", - "ed25519-dalek", - "elliptic-curve 0.13.8", - "generic-array", - "getrandom", - "hex-literal 0.4.1", - "hmac", - "k256 0.13.1", - "num-derive 0.4.2", - "num-integer", - "num-traits", - "p256", - "rand", - "rand_chacha", - "sec1 0.7.3", - "sha2 0.10.8", - "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", - "soroban-wasmi", - "static_assertions", - "stellar-strkey", - "wasmparser", -] - -[[package]] -name = "soroban-env-macros" -version = "21.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" -dependencies = [ - "itertools 0.11.0", - "proc-macro2 1.0.70", - "quote 1.0.33", - "serde", - "serde_json", - "stellar-xdr", - "syn 2.0.42", -] - -[[package]] -name = "soroban-ledger-snapshot" -version = "21.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84589856911dfd6731695c9b51c858aed6d4540118c0a1e5c4c858ea13bc744c" -dependencies = [ - "serde", - "serde_json", - "serde_with", - "soroban-env-common", - "soroban-env-host", - "thiserror", -] - -[[package]] -name = "soroban-rlp" -version = "0.1.0" -dependencies = [ - "soroban-sdk", -] - -[[package]] -name = "soroban-sdk" -version = "21.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b888f866ce621257311dd779ea49280ce29faa5ee37f22270ba7c32a8fe25d" -dependencies = [ - "arbitrary", - "bytes-lit", - "ctor", - "ed25519-dalek", - "rand", - "serde", - "serde_json", - "soroban-env-guest", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk-macros", - "stellar-strkey", -] - -[[package]] -name = "soroban-sdk-macros" -version = "21.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c2173f1aacd56b4405eed71cb2a9694dff99d51ba72d4f0cbc5e4961fdabf4" -dependencies = [ - "crate-git-revision", - "darling", - "itertools 0.11.0", - "proc-macro2 1.0.70", - "quote 1.0.33", - "rustc_version", - "sha2 0.10.8", - "soroban-env-common", - "soroban-spec", - "soroban-spec-rust", - "stellar-xdr", - "syn 2.0.42", -] - -[[package]] -name = "soroban-spec" -version = "21.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7705bffbcc747c08e81698b87b4a787f8b268c25d88f777160091dc1ee8121cb" -dependencies = [ - "base64 0.13.1", - "stellar-xdr", - "thiserror", - "wasmparser", -] - -[[package]] -name = "soroban-spec-rust" -version = "21.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48207ebc8616c2804a17203d1d86c53c3d3c804b682cbab011a135893db1cf78" -dependencies = [ - "prettyplease 0.2.15", - "proc-macro2 1.0.70", - "quote 1.0.33", - "sha2 0.10.8", - "soroban-spec", - "stellar-xdr", - "syn 2.0.42", - "thiserror", -] - -[[package]] -name = "soroban-wasmi" -version = "0.31.1-soroban.20.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" -dependencies = [ - "smallvec", - "spin", - "wasmi_arena", - "wasmi_core", - "wasmparser-nostd", -] - -[[package]] -name = "soroban-xcall-lib" -version = "0.1.0" -dependencies = [ - "soroban-sdk", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" version = "0.6.0" @@ -2913,39 +2218,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stellar-strkey" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" -dependencies = [ - "base32", - "crate-git-revision", - "thiserror", -] - -[[package]] -name = "stellar-xdr" -version = "21.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" -dependencies = [ - "arbitrary", - "base64 0.13.1", - "crate-git-revision", - "escape-bytes", - "hex", - "serde", - "serde_with", - "stellar-strkey", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "strum" version = "0.25.0" @@ -3040,7 +2312,7 @@ checksum = "cda53c85447577769cdfc94c10a56f34afef2c00e4108badb57fce6b1a0c75eb" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 1.5.3", + "ed25519", "ed25519-consensus", "flex-error", "futures", @@ -3082,7 +2354,7 @@ checksum = "c943f78c929cdf14553842f705f2c30324bc35b9179caaa5c9b80620f60652e6" dependencies = [ "bytes", "flex-error", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost 0.11.9", "prost-types", @@ -3101,7 +2373,7 @@ dependencies = [ "cosmwasm-std", "cw-multi-test 0.16.4", "hex", - "hex-literal 0.3.4", + "hex-literal", "ibc-proto", "prost 0.11.9", "serde", @@ -3134,8 +2406,6 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa", - "serde", "time-core", "time-macros", ] @@ -3256,98 +2526,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote 1.0.33", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" - -[[package]] -name = "wasmi_core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] - -[[package]] -name = "wasmparser" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" -dependencies = [ - "indexmap 2.1.0", - "semver", -] - -[[package]] -name = "wasmparser-nostd" -version = "0.100.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" -dependencies = [ - "indexmap-nostd", -] - [[package]] name = "which" version = "4.4.0" @@ -3359,37 +2537,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.45.0" @@ -3438,22 +2585,6 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3466,12 +2597,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3484,12 +2609,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3502,18 +2621,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3526,12 +2633,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3544,12 +2645,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3562,12 +2657,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3580,12 +2669,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "winnow" version = "0.5.30" @@ -3595,36 +2678,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "xcall" -version = "0.1.0" -dependencies = [ - "soroban-rlp", - "soroban-sdk", - "soroban-xcall-lib", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "zeroize" version = "1.8.1" diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index ea0dadff..60456f1f 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); + let length_bytes = utils::to_bytes_u64(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); + let length_bytes=utils::to_bytes_u64(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,13 +86,13 @@ module sui_rlp::encoder { } public fun encode_u64(num:u64):vector{ - let vec= utils::to_bytes_u64(num); + let vec= utils::to_bytes_u64(num,true); encode(&vec) } public fun encode_u128(num:u128):vector{ - let vec= utils::to_bytes_u128(num); + let vec= utils::to_bytes_u128(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 e09d9d88..5dd8c721 100644 --- a/contracts/sui/libs/sui_rlp/sources/utils.move +++ b/contracts/sui/libs/sui_rlp/sources/utils.move @@ -41,25 +41,28 @@ module sui_rlp::utils { } - public fun to_bytes_u128(number:u128):vector{ + public fun to_bytes_u128(number:u128,signed:bool):vector{ let bytes=bcs::to_bytes(&number); - to_signed_bytes(bytes) + to_signed_bytes(bytes,signed) } - public fun to_bytes_u64(number:u64):vector{ + public fun to_bytes_u64(number:u64,signed:bool):vector{ let bytes=bcs::to_bytes(&number); - to_signed_bytes(bytes) + to_signed_bytes(bytes,signed) } - public fun to_bytes_u32(number: u32): vector { + public fun to_bytes_u32(number: u32,signed:bool): vector { let bytes=bcs::to_bytes(&number); - to_signed_bytes(bytes) + to_signed_bytes(bytes,signed) } - fun to_signed_bytes(mut bytes:vector):vector{ + fun to_signed_bytes(mut bytes:vector,signed:bool):vector{ bytes.reverse(); let truncated=truncate_zeros(&bytes); + if(signed==false){ + return truncated + }; let first_byte=*truncated.borrow(0); if (first_byte >= 128) { @@ -114,7 +117,7 @@ module sui_rlp::utils_test { #[test] fun test_u32_conversion() { let num= (122 as u32); - let bytes= utils::to_bytes_u32(num); + let bytes= utils::to_bytes_u32(num,true); let converted=utils::from_bytes_u32(&bytes); assert!(num==converted,0x01); @@ -123,7 +126,7 @@ module sui_rlp::utils_test { #[test] fun test_u64_conversion() { let num= (55000 as u64); - let bytes= utils::to_bytes_u64(num); + let bytes= utils::to_bytes_u64(num,true); let converted=utils::from_bytes_u64(&bytes); std::debug::print(&bytes); std::debug::print(&converted); @@ -134,7 +137,7 @@ module sui_rlp::utils_test { #[test] fun test_u128_conversion() { let num= (1222223333 as u128); - let bytes= utils::to_bytes_u128(num); + let bytes= utils::to_bytes_u128(num,true); std::debug::print(&bytes); let converted=utils::from_bytes_u128(&bytes); std::debug::print(&converted); From 6863f96da329380bf4d5aca824765df9b6ad0924 Mon Sep 17 00:00:00 2001 From: gcranju Date: Mon, 25 Nov 2024 15:25:50 +0545 Subject: [PATCH 57/75] fix: changes on sui cluster connection --- .../lib/openzeppelin-contracts-upgradeable | 2 +- .../sui/libs/sui_rlp/sources/encoder.move | 2 +- .../cluster_connection/cluster_entry.move | 9 +- .../cluster_connection/cluster_state.move | 142 +++++++------ .../cluster_connection/signatures.move | 196 ------------------ contracts/sui/xcall/sources/utils.move | 3 +- 6 files changed, 91 insertions(+), 263 deletions(-) delete mode 100644 contracts/sui/xcall/sources/cluster_connection/signatures.move diff --git a/contracts/evm/lib/openzeppelin-contracts-upgradeable b/contracts/evm/lib/openzeppelin-contracts-upgradeable index 625fb3c2..4ca003c9 160000 --- a/contracts/evm/lib/openzeppelin-contracts-upgradeable +++ b/contracts/evm/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 625fb3c2b2696f1747ba2e72d1e1113066e6c177 +Subproject commit 4ca003c9635d2c16756cf8c9db6760e2d3653dee diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index 99bfa7e6..5e2b93b1 100644 --- a/contracts/sui/libs/sui_rlp/sources/encoder.move +++ b/contracts/sui/libs/sui_rlp/sources/encoder.move @@ -86,7 +86,7 @@ module sui_rlp::encoder { } public fun encode_u32(num:u32):vector{ - let vec= utils::to_bytes_u32(num); + let vec= utils::to_bytes_u32(num,true); encode(&vec) } diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move index 0bbf2fdc..1fab2e8f 100644 --- a/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move @@ -2,7 +2,7 @@ module xcall::cluster_entry{ use xcall::main::{Self as xcall}; use xcall::xcall_state::{Self,Storage as XCallState,ConnCap}; - use xcall::cluster_state::{Self,get_state,get_state_mut,validate_admin_cap, Validator, AdminCap}; + use xcall::cluster_state::{Self,get_state,get_state_mut,validate_admin_cap, AdminCap}; use xcall::xcall_utils::{Self as utils}; use std::string::{String}; @@ -33,7 +33,7 @@ module xcall::cluster_entry{ cluster_state::get_fee(state,&net_id,response) } - entry fun set_validators(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,validator_pubkey:vector,threshold:u64,_ctx: &mut TxContext){ + entry fun set_validators(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,validator_pubkey:vector>,threshold:u64,_ctx: &mut TxContext){ validate_admin_cap(cap,connection_id); let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),connection_id); cluster_state::set_validators(state,validator_pubkey,threshold); @@ -45,7 +45,7 @@ module xcall::cluster_entry{ cluster_state::set_validator_threshold(state,threshold); } - entry fun get_validators(states: &XCallState,connection_id:String,_ctx: &TxContext):vector{ + entry fun get_validators(states: &XCallState,connection_id:String,_ctx: &TxContext):vector>{ let state = get_state(states.get_connection_states(),connection_id); cluster_state::get_validators(state) } @@ -57,8 +57,7 @@ module xcall::cluster_entry{ entry fun recieve_message_with_signatures(xcall:&mut XCallState,cap:&ConnCap,src_net_id:String,sn:u128,msg:vector,signatures:vector>,ctx: &mut TxContext){ let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); - let message_hash = utils::get_message_hash(src_net_id, sn, msg); - cluster_state::verify_signatures(state, message_hash, signatures); + cluster_state::verify_signatures(state, src_net_id, sn, msg, signatures); cluster_state::check_save_receipt(state, src_net_id, sn); xcall::handle_message(xcall, cap,src_net_id, msg,ctx); } diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_state.move b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move index 5a0b7dcb..07742c14 100644 --- a/contracts/sui/xcall/sources/cluster_connection/cluster_state.move +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move @@ -2,13 +2,15 @@ module xcall::cluster_state { use std::string::{String}; use sui::vec_map::{Self, VecMap}; use xcall::xcall_utils::{Self as utils}; - use xcall::signatures::{pubkey_to_sui_address, verify_signature, get_pubkey_from_signature}; use sui::coin::{Self}; use sui::balance::{Self, Balance}; use sui::sui::SUI; use sui::bag::{Bag, Self}; use sui::event; - + use sui::address::{Self}; + use sui::hash::{Self}; + use 0x2::ecdsa_k1::{secp256k1_ecrecover, decompress_pubkey}; + //ERRORS const VerifiedSignaturesLessThanThreshold: u64 = 100; const NotEnoughSignatures: u64 = 101; @@ -16,9 +18,13 @@ module xcall::cluster_state { const ValidatorCountMustBeGreaterThanThreshold: u64 = 105; const InvalidAdminCap: u64 = 106; + /* hash algorithm*/ + const KECCAK256: u8 = 0x00; + const SHA256: u8 = 0x01; + //EVENTS public struct ValidatorSetAdded has copy, drop { - validators: vector, + validators: vector>, threshold: u64 } @@ -55,18 +61,13 @@ module xcall::cluster_state { nid: String, } - public struct Validator has store, drop, copy{ - pub_key:vector, - sui_address:address, - } - public struct State has store{ message_fee: VecMap, response_fee: VecMap, receipts: VecMap, conn_sn: u128, balance: Balance, - validators: vector, + validators: vector>, validators_threshold:u64, } @@ -83,14 +84,6 @@ module xcall::cluster_state { } } - fun get_validator(pub_key:String):Validator{ - let (pubkey,sui_address)=pubkey_to_sui_address(&pub_key); - Validator{ - pub_key:pubkey, - sui_address:sui_address - } - } - public(package) fun get_next_conn_sn(self:&mut State):u128 { let sn=self.conn_sn+1; self.conn_sn=sn; @@ -141,33 +134,60 @@ module xcall::cluster_state { } - public(package) fun verify_signatures(self:&State,message_hash:vector,signatures:vector>){ - let threshold=self.get_validator_threshold(); - let validators=self.get_validators().map!(|validator| validator.pub_key); + public(package) fun verify_signatures( + self: &State, + src_net_id: String, + sn: u128, + msg: vector, + signatures: vector> + ) { + let message_hash = utils::get_message_hash(src_net_id, sn, msg); + let threshold = self.get_validator_threshold(); + let validators = self.get_validators(); + + // Ensure the number of signatures meets the threshold assert!(signatures.length() >= threshold, NotEnoughSignatures); + let mut i = 0; let mut unique_verified_pubkey = vector::empty(); + while (i < signatures.length()) { - let signature = signatures.borrow(i); - let pub_key = get_pubkey_from_signature(signature); - if (validators.contains(&pub_key)) { - - if (verify_signature(&pub_key,signature,&message_hash)) { + let mut signature = *signatures.borrow(i); + let mut recovery_code = signature.pop_back(); + let code = 27 as u8; + + if (recovery_code >= code) { + recovery_code = recovery_code - code; + }; + + signature.push_back(recovery_code); - if (!unique_verified_pubkey.contains(&pub_key)){ - unique_verified_pubkey.push_back(pub_key); - }; + let pub_key = decompress_pubkey( + &secp256k1_ecrecover(&signature, &message_hash, KECCAK256) + ); - if (unique_verified_pubkey.length() >= threshold) { - return - }; + if (validators.contains(&pub_key)) { + if (!unique_verified_pubkey.contains(&pub_key)) { + unique_verified_pubkey.push_back(pub_key); + }; + + // Exit early if the threshold is met + if (unique_verified_pubkey.length() >= threshold) { + return; }; }; - i=i+1; + + i = i + 1; }; - assert!(unique_verified_pubkey.length() >= threshold, VerifiedSignaturesLessThanThreshold); + + // Assert that the unique verified public keys meet the threshold + assert!( + unique_verified_pubkey.length() >= threshold, + VerifiedSignaturesLessThanThreshold + ); } + public(package) fun get_validator_threshold(self:&State):u64{ self.validators_threshold } @@ -177,12 +197,11 @@ module xcall::cluster_state { self.validators_threshold=threshold } - public(package) fun set_validators(self:&mut State,validator_pub_keys:vector,threshold:u64){ + public(package) fun set_validators(self:&mut State,validator_pub_keys:vector>,threshold:u64){ self.validators=vector::empty(); let mut validator_pub_keys = validator_pub_keys; while (validator_pub_keys.length() > 0) { - let validator_pub_key = validator_pub_keys.pop_back(); - let validator=get_validator(validator_pub_key); + let validator = validator_pub_keys.pop_back(); if(self.validators.contains(&validator)){ continue }; @@ -193,7 +212,7 @@ module xcall::cluster_state { event::emit(ValidatorSetAdded { validators: self.validators, threshold }); } - public(package) fun get_validators(self:&State):vector{ + public(package) fun get_validators(self:&State):vector>{ self.validators } @@ -222,10 +241,10 @@ module xcall::cluster_state_tests { let mut state = xcall::cluster_state::create_state(); let validators = vector[ - b"AJ6snNNaDhPZLg06AkcvYL0TZe4+JgoWtZKG/EJmzdWi".to_string(), - b"ADDxHCpQUcFsy5H5Gy01uv7LoISvtJLfgVGfWy4bLrjO".to_string(), - b"AL0hUNIiz5Q2fv0siZc75ce3aOyUpiiI+Q8Rmfay4K/X".to_string(), - b"ALnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA".to_string() + x"045b419bdec0d2bbc16ce8ae144ff8e825123fd0cb3e36d0075b6d8de5aab53388ac8fb4c28a8a3843f3073cdaa40c943f74737fc0cea4a95f87778affac738190", + x"04ae36a8bfd8cf6586f34c688528894835f5e7c19d36689bac5460656b613c5eabf1fa982212aa27caece23a2708eb3c8936e132b9fd82c5aee2aa4b06917b5713", + x"04f8c0afc6e4fa149e17fbb0f4d09647971bd016291e9ac66d0a708ec82fc8d5d2ac878d81b7d3f1d37f1013439fc3eb58a4df2f802f931c791c5d81b09034f337", + x"046bc928ee4932efd619ec4c00e0591e932cf2cfef13a59f6027da1c6cba36b35d91238b54aece19825025a9c7cb0bc58a60d5c49e7fc8e5b39fcc4c2193f5feb2" ]; set_validators(&mut state, validators, 2); @@ -244,8 +263,8 @@ module xcall::cluster_state_tests { #[test] fun test_set_get_threshold(): State { let mut state = test_add_validator(); - set_validator_threshold(&mut state, 2); - assert!(get_validator_threshold(&state)==2); + set_validator_threshold(&mut state, 1); + assert!(get_validator_threshold(&state)==1); state } @@ -303,12 +322,15 @@ module xcall::cluster_state_tests { #[test] #[expected_failure(abort_code = 101)] fun test_verify_signatures_less_than_threshold(): State { - let state = test_set_get_threshold(); - - let msg: vector = x"6162636465666768"; - let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200"]; + let state = test_add_validator(); + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; - xcall::cluster_state::verify_signatures(&state, msg, signatures); + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81b", + ]; + + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); state } @@ -316,24 +338,28 @@ module xcall::cluster_state_tests { #[expected_failure(abort_code = 100)] fun test_verify_signatures_invalid(): State { let state = test_set_get_threshold(); - let msg: vector = x"6162636465666768"; - let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200", - x"00c6d94cc625e73e036852316d228e578893aad2a7b21febc08f92a5d53978154782e4a0551ced23fb92c765b4cb4715e231de0235e2b641b81a36b9f2a3f8630d9eac9cd35a0e13d92e0d3a02472f60bd1365ee3e260a16b59286fc4266cdd5a2" - ]; + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; + + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81c", + ]; - xcall::cluster_state::verify_signatures(&state, msg, signatures); + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); state } #[test] fun test_verify_signatures(): State { let state = test_set_get_threshold(); - let msg: vector = x"6162636465666768"; - let signatures = vector[x"00bb0a7ba4a242a4988c820b94a8df9b312e9e7cf4f8302b53ee2e046e76da86eae9c15296a421b8dddb29cafa8d50523e0b04300216e393d45c0739a0eab8e60cb9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200", - x"00c6d94cc625e73e036852316d229e578893aad2a7b21febc08f92a5d53978154782e4a0551ced23fb92c765b4cb4715e231de0235e2b641b81a36b9f2a3f8630d9eac9cd35a0e13d92e0d3a02472f60bd1365ee3e260a16b59286fc4266cdd5a2" - ]; + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; + + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81b", + ]; - xcall::cluster_state::verify_signatures(&state, msg, signatures); + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); state } diff --git a/contracts/sui/xcall/sources/cluster_connection/signatures.move b/contracts/sui/xcall/sources/cluster_connection/signatures.move deleted file mode 100644 index 81fe18ad..00000000 --- a/contracts/sui/xcall/sources/cluster_connection/signatures.move +++ /dev/null @@ -1,196 +0,0 @@ -#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] - -module xcall::signatures { - use sui::linked_table::{Self, LinkedTable}; - use sui::types as sui_types; - use std::string::{Self, String}; - use sui::event; - use sui::hash::{Self}; - use sui::vec_map::{Self, VecMap}; - use sui::table::{Table,Self}; - use sui::bcs::{Self}; - use sui::address::{Self}; - use sui::{ed25519::ed25519_verify}; - use sui::{ecdsa_k1::secp256k1_verify}; - use sui::{ecdsa_r1::secp256r1_verify}; - - const BASE64_CHARS: vector = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - const PADDING_CHAR: vector = b"="; - - /** signature schemes*/ - const FlagED25519 :u8= 0x00; - const FlagSecp256k1 :u8= 0x01; - const FlagSecp256r1 :u8= 0x02; - const FlagMultiSig :u8= 0x03; - - /* hash algorithm*/ - const KECCAK256: u8 = 0x00; - const SHA256: u8 = 0x01; - - public fun decode(input:&String):vector{ - let char_index=get_char_map(); - let mut output = vector::empty(); - let input_bytes = input.as_bytes(); - let mut i = 0; - while( i < input_bytes.length()){ - let b1 = *char_index.get(input_bytes.borrow(i)); - let b2 = *char_index.get(input_bytes.borrow(i + 1)); - let b3 = if (i + 2 < input_bytes.length()) { - let key=input_bytes.borrow(i+2); - let val:u32 = if (char_index.contains(key)) { - *char_index.get(key) - } else { - 64 - }; - val - } else { - 64 - }; - let b4 = if (i + 3 < input_bytes.length()) { - let key=input_bytes.borrow(i+3); - let val:u32= if (char_index.contains(key)) { - *char_index.get(key) - } else { - 64 - }; - val - } else { - 64 - }; - let triple = (b1 << 18) | (b2 << 12) | (b3 << 6) | b4; - output.push_back(((triple >> 16) & 0xFF) as u8); - if (b3 != 64) { - output.push_back(((triple >> 8) & 0xFF) as u8); - }; - if (b4 != 64) { - output.push_back((triple & 0xFF) as u8); - }; - i = i+4; - }; - output - } - - fun get_char_map():VecMap{ - let mut char_map = vec_map::empty(); - let mut i:u64=0; - while( i < BASE64_CHARS.length()){ - let c=*BASE64_CHARS.borrow(i); - char_map.insert(c,(i as u32)); - i=i+1; - }; - char_map - } - - public(package) fun pubkey_to_sui_address(pubkey:&String):(vector,address){ - let pubkey_bytes = decode(pubkey); - let sui_address = address::from_bytes(hash::blake2b256(&pubkey_bytes)); - (pubkey_bytes,sui_address) - } - - public(package) fun get_pubkey_from_signature(raw_signature:&vector):vector{ - let (signature,mut pubkey,scheme)= split_signature(raw_signature); - pubkey.insert(scheme, 0); - pubkey - } - - public(package) fun verify_signature(pubkey:&vector,raw_signature:&vector,msg:&vector):bool{ - let flag=*pubkey.borrow(0); - let public_key=slice_vector(pubkey,1,pubkey.length()-1); - let (signature,pub,_scheme)= split_signature(raw_signature); - - let intent_msg= get_intent_message(msg); - let digest= hash::blake2b256(&intent_msg); - - let verify= if(flag==FlagED25519){ - - ed25519_verify(&signature,&public_key,&digest) - - }else if(flag==FlagSecp256k1){ - - secp256k1_verify(&signature,&public_key,msg,SHA256) - - }else if (flag==FlagSecp256r1){ - - secp256r1_verify(&signature,&public_key,msg,SHA256) - }else { - return false - }; - verify - } - - fun get_intent_message(msg:&vector):vector{ - let mut intent_message:vector =vector::empty(); - intent_message.push_back(0x00); - intent_message.push_back(0x00); - intent_message.push_back(0x00); - intent_message.append(*msg); - intent_message - } - - fun split_signature(raw_signature:&vector):(vector,vector,u8){ - let scheme=*raw_signature.borrow(0); - let length= if (scheme==FlagED25519){32}else{33}; - let signature=slice_vector(raw_signature,1,64); - let pubkey=slice_vector(raw_signature,raw_signature.length()-length,length); - return (signature,pubkey,scheme) - } - - public fun slice_vector(vec: &vector, start: u64, length: u64): vector { - let mut result = vector::empty(); - let mut i = 0; - while (i < length) { - let value = *vector::borrow(vec, start + i); - vector::push_back(&mut result, value); - i = i + 1; - }; - result - } - -} - -#[test_only] -module xcall::signature_tests { - use sui::hash::{Self}; - use xcall::signatures::{decode, pubkey_to_sui_address, get_pubkey_from_signature, verify_signature}; - use std::debug::print; - use sui::{ed25519::ed25519_verify}; - - #[test] - fun test_base64(){ - let input = b"SGVsbG8sIFdvcmxkIQ==".to_string(); - let decoded = decode(&input); - assert!(decoded==b"Hello, World!",0x01); - } - - #[test] - fun test_pubkey_to_sui_address(){ - let input = b"AL0hUNIiz5Q2fv0siZc75ce3aOyUpiiI+Q8Rmfay4K/X".to_string(); - let (_pubkey_bytes,sui_address) = pubkey_to_sui_address(&input); - assert!(sui_address==@0xdefd91e5bfdb22edc6cabe1e9490549377047ec476354241910685876f1af34a,0x01); - } - - #[test] - fun test_get_pubkey_from_signature(){ - let input = b"ADQTOEZPWpPeZc9auXhwyciH7Pw6ny8xSxR+JVnSBnNktKAgTgxJ2EwHcErw55enK4w6c1uYaUv2gfY8Vb3X+Q6UHzNBcHIzSfMdKerK6XOCCmHbsI9Tpa8lGFvkm+Gbwg==".to_string(); - let pubkey = get_pubkey_from_signature(&decode(&input)); - assert!(pubkey==decode(&b"AJQfM0FwcjNJ8x0p6srpc4IKYduwj1OlryUYW+Sb4ZvC".to_string()),0x01); - } - - #[test] - fun test_verify_signature(){ - let data=x"6162636465666768"; - let pubkey = b"ALnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA".to_string(); - let signature = b"ALsKe6SiQqSYjIILlKjfmzEunnz0+DArU+4uBG522obq6cFSlqQhuN3bKcr6jVBSPgsEMAIW45PUXAc5oOq45gy5xu4WMO8+cRFEpkjbBruyKE9ydM++5T/87lA8waSSAA==".to_string(); - let verify = verify_signature(&decode(&pubkey),&decode(&signature),&data); - assert!(verify,0x01); - - let data=x"6162636465666768"; - let pubkey = x"b9c6ee1630ef3e711144a648db06bbb2284f7274cfbee53ffcee503cc1a49200"; - let signature = x"c2ac48f07c55a8da3a0a762032a4956b08c63eebbe3f7a3f4cc56cd7fa23b1a3f7dec39e8e112162797d7c98f87d07314abff3cf594e42c27e942d3af43dcc07"; - - - let verify = ed25519_verify(&signature, &pubkey, &data); - assert!(verify,0x01); - - } -} diff --git a/contracts/sui/xcall/sources/utils.move b/contracts/sui/xcall/sources/utils.move index 889691b1..489a4481 100644 --- a/contracts/sui/xcall/sources/utils.move +++ b/contracts/sui/xcall/sources/utils.move @@ -32,8 +32,7 @@ module xcall::xcall_utils { vector::push_back(&mut list, encoder::encode_u128(sn)); vector::push_back(&mut list, encoder::encode(&msg)); let encoded=encoder::encode_list(&list,false); - hash::keccak256(&encoded) - + encoded } public fun id_to_hex_string(id:&ID): String { From a47a8c639b341b5d78aabbc104935fe8f55942e2 Mon Sep 17 00:00:00 2001 From: gcranju Date: Tue, 26 Nov 2024 11:45:27 +0545 Subject: [PATCH 58/75] fix: stellar cluster connection finalize --- Cargo.lock | 1 + .../lib/openzeppelin-contracts-upgradeable | 2 +- contracts/soroban/Cargo.lock | 1 + .../contracts/cluster-connection/Cargo.toml | 1 + .../cluster-connection/src/contract.rs | 26 +++- .../cluster-connection/src/helpers.rs | 60 +++----- .../cluster-connection/src/storage.rs | 6 +- .../contracts/cluster-connection/src/test.rs | 137 ++++++++++++++---- .../soroban/libs/soroban-rlp/src/utils.rs | 2 +- scripts/optimize-stellar.sh | 2 +- 10 files changed, 164 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b673ca4b..7ceede42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,7 @@ dependencies = [ name = "cluster-connection" version = "0.0.0" dependencies = [ + "soroban-rlp", "soroban-sdk", ] diff --git a/contracts/evm/lib/openzeppelin-contracts-upgradeable b/contracts/evm/lib/openzeppelin-contracts-upgradeable index 625fb3c2..4ca003c9 160000 --- a/contracts/evm/lib/openzeppelin-contracts-upgradeable +++ b/contracts/evm/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 625fb3c2b2696f1747ba2e72d1e1113066e6c177 +Subproject commit 4ca003c9635d2c16756cf8c9db6760e2d3653dee diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index b8c8a485..8d9e9437 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -155,6 +155,7 @@ dependencies = [ name = "cluster-connection" version = "0.0.0" dependencies = [ + "soroban-rlp", "soroban-sdk", ] diff --git a/contracts/soroban/contracts/cluster-connection/Cargo.toml b/contracts/soroban/contracts/cluster-connection/Cargo.toml index be7f7e11..e6f6b482 100644 --- a/contracts/soroban/contracts/cluster-connection/Cargo.toml +++ b/contracts/soroban/contracts/cluster-connection/Cargo.toml @@ -10,6 +10,7 @@ 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 index 86377d61..b02d54ee 100644 --- a/contracts/soroban/contracts/cluster-connection/src/contract.rs +++ b/contracts/soroban/contracts/cluster-connection/src/contract.rs @@ -28,7 +28,7 @@ impl ClusterConnection { } pub fn set_admin(env: Env, address: Address) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; + helpers::ensure_admin(&env)?; storage::store_admin(&env, address); Ok(()) } @@ -45,6 +45,12 @@ impl ClusterConnection { 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, @@ -114,11 +120,11 @@ impl ClusterConnection { Ok(()) } - pub fn update_validators(env: Env, addresses: Vec
, threshold: u32) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; + 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 addresses.clone() { + for address in pub_keys.clone() { if !validators.contains(&address) { validators.push_back(address); } @@ -127,7 +133,8 @@ impl ClusterConnection { return Err(ContractError::ThresholdExceeded); } - storage::store_validators(&env, addresses); + storage::store_validators(&env, pub_keys); + storage::store_validator_threshold(&env, threshold); Ok(()) } @@ -137,7 +144,7 @@ impl ClusterConnection { } pub fn set_validators_threshold(env: Env, threshold: u32) -> Result<(), ContractError> { - helpers::ensure_relayer(&env)?; + helpers::ensure_admin(&env)?; let validators = storage::get_validators(&env).unwrap(); if (validators.len() as u32) < threshold { return Err(ContractError::ThresholdExceeded); @@ -146,11 +153,16 @@ impl ClusterConnection { Ok(()) } - pub fn get_validators(env: Env) -> Result, ContractError> { + 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) } diff --git a/contracts/soroban/contracts/cluster-connection/src/helpers.rs b/contracts/soroban/contracts/cluster-connection/src/helpers.rs index f34d968e..f37ed14f 100644 --- a/contracts/soroban/contracts/cluster-connection/src/helpers.rs +++ b/contracts/soroban/contracts/cluster-connection/src/helpers.rs @@ -1,7 +1,6 @@ -use core::panic; - -use soroban_sdk::{token, Address, Bytes, BytesN, Env, FromVal, Map, String, Vec}; +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)?; @@ -57,25 +56,6 @@ pub fn transfer_token( Ok(()) } -pub fn compress_public_keys(e: &Env, uncompressed_public_key: BytesN<65>) -> Bytes { - let uncompressed_pub_key_array = uncompressed_public_key.to_array(); - if uncompressed_pub_key_array[0] != 0x04 { - return Bytes::from_array(e, &[0u8; 33]); - } - - let x = &uncompressed_pub_key_array[1..33]; - let y = &uncompressed_pub_key_array[33..65]; - - let prefix = if y[31] % 2 == 0 { 0x02 } else { 0x03 }; - - let mut compressed_pub_key_array = [0u8; 33]; - compressed_pub_key_array[0] = prefix; - compressed_pub_key_array[1..].copy_from_slice(x); - let compressed_pub_key = Bytes::from_array(e, &compressed_pub_key_array); - - compressed_pub_key -} - pub fn verify_signatures( e: &Env, signatures: Vec>, @@ -98,19 +78,17 @@ pub fn verify_signatures( 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_id = r_s_v[64] as u32; // recovery ID - + 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 uncompressed_public_key = e.crypto().secp256k1_recover(&message_hash, &signature, recovery_id); + let public_key = e.crypto().secp256k1_recover(&message_hash, &signature, recovery_code as u32); - let compressed_pub_key = compress_public_keys(e, uncompressed_public_key); - - let stellar_address = Address::from_string_bytes(&compressed_pub_key); - - if validators.contains(&stellar_address) { - if !unique_validators.contains_key(stellar_address.clone()) { - unique_validators.set(stellar_address, count); + if validators.contains(&public_key) { + if !unique_validators.contains_key(public_key.clone()) { + unique_validators.set(public_key, count); count += 1; } } @@ -121,12 +99,15 @@ pub fn verify_signatures( pub fn get_encoded_message(e: &Env, src_network: &String, conn_sn: &u128, message: &Bytes) -> Bytes { - let mut result = Bytes::from_val(e, &src_network.to_val()); - result.extend_from_slice(&conn_sn.to_le_bytes()); - result.append(message); - result + 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); @@ -135,6 +116,13 @@ pub fn call_xcall_handle_message(e: &Env, nid: &String, msg: Bytes) -> Result<() 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); diff --git a/contracts/soroban/contracts/cluster-connection/src/storage.rs b/contracts/soroban/contracts/cluster-connection/src/storage.rs index 538b21dd..bdc017a1 100644 --- a/contracts/soroban/contracts/cluster-connection/src/storage.rs +++ b/contracts/soroban/contracts/cluster-connection/src/storage.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{vec, Address, Env, String, Vec}; +use soroban_sdk::{Address, BytesN, Env, String, Vec}; use crate::{ errors::ContractError, @@ -117,7 +117,7 @@ pub fn get_validators_threshold(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } -pub fn get_validators(e: &Env) -> Result, ContractError> { +pub fn get_validators(e: &Env) -> Result>, ContractError> { e.storage() .instance() .get(&StorageKey::Validators) @@ -160,7 +160,7 @@ pub fn store_validator_threshold(e: &Env, threshold: u32) { e.storage().instance().set(&StorageKey::ValidatorThreshold, &threshold); } -pub fn store_validators(e: &Env, validators: Vec
) { +pub fn store_validators(e: &Env, validators: Vec>) { e.storage().instance().set(&StorageKey::Validators, &validators); } diff --git a/contracts/soroban/contracts/cluster-connection/src/test.rs b/contracts/soroban/contracts/cluster-connection/src/test.rs index bab627e0..fe375ba5 100644 --- a/contracts/soroban/contracts/cluster-connection/src/test.rs +++ b/contracts/soroban/contracts/cluster-connection/src/test.rs @@ -13,15 +13,14 @@ use crate::{ types::InitializeMsg, }; use soroban_sdk::{ - symbol_short, - testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, - token, vec, Address, Bytes, Env, IntoVal, String, Symbol, Vec, + 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, @@ -33,12 +32,14 @@ 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: env.register_contract_wasm(None, xcall::WASM), + 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, "icon"), + nid: String::from_str(&env, "0x2.icon"), upgrade_authority: Address::generate(&env), env, token_admin, @@ -49,12 +50,13 @@ impl TestContext { self.env.mock_all_auths(); client.initialize(&InitializeMsg { - admin: self.relayer.clone(), + 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>) { @@ -75,6 +77,7 @@ fn get_dummy_initialize_msg(env: &Env) -> InitializeMsg { } } + #[test] fn test_initialize() { let ctx = TestContext::default(); @@ -83,7 +86,7 @@ fn test_initialize() { ctx.init_context(&client); let admin = client.get_admin(); - assert_eq!(admin, ctx.relayer) + assert_eq!(admin, ctx.admin) } #[test] @@ -109,7 +112,7 @@ fn test_set_admin() { assert_eq!( ctx.env.auths(), std::vec![( - ctx.relayer.clone(), + ctx.admin.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( client.address.clone(), @@ -353,26 +356,25 @@ fn test_add_validator() { ctx.init_context(&client); - let validator1 = Address::generate(&ctx.env); - let validator2 = Address::generate(&ctx.env); - let validator3 = Address::generate(&ctx.env); + 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(validator1.clone()); - validators.push_back(validator2.clone()); - validators.push_back(validator3.clone()); - + 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.relayer, + ctx.admin.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( client.address.clone(), Symbol::new(&ctx.env, "update_validators"), - (validators.clone(), 3,).into_val(&ctx.env) + (validators.clone(), 3_u32,).into_val(&ctx.env) )), sub_invocations: std::vec![] } @@ -393,23 +395,23 @@ fn test_set_threshold() { ctx.init_context(&client); - let validator1 = Address::generate(&ctx.env); - let validator2 = Address::generate(&ctx.env); - let validator3 = Address::generate(&ctx.env); + 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(validator1.clone()); - validators.push_back(validator2.clone()); - validators.push_back(validator3.clone()); - + 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.relayer, + ctx.admin.clone(), AuthorizedInvocation { function: AuthorizedFunction::Contract(( client.address.clone(), @@ -426,3 +428,88 @@ fn test_set_threshold() { 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/libs/soroban-rlp/src/utils.rs b/contracts/soroban/libs/soroban-rlp/src/utils.rs index 403cac52..b359078c 100644 --- a/contracts/soroban/libs/soroban-rlp/src/utils.rs +++ b/contracts/soroban/libs/soroban-rlp/src/utils.rs @@ -53,7 +53,7 @@ pub fn bytes_to_u64(bytes: Bytes) -> u64 { } pub fn u128_to_bytes(env: &Env, number: u128) -> Bytes { - let mut bytes = bytes!(&env, 0x00); + let mut bytes: Bytes = Bytes::new(&env); let mut i = 15; let mut leading_zero = true; while i >= 0 { diff --git a/scripts/optimize-stellar.sh b/scripts/optimize-stellar.sh index b527d71f..691c6e4b 100755 --- a/scripts/optimize-stellar.sh +++ b/scripts/optimize-stellar.sh @@ -11,7 +11,7 @@ cargo build --target wasm32-unknown-unknown --release for WASM in $build_directory/*.wasm; do NAME=$(basename "$WASM" .wasm)${SUFFIX}.wasm echo "Optimizing $NAME ... $WASM" - stellar contract optimize --wasm "$WASM" + /usr/local/bin/stellar2 contract optimize --wasm "$WASM" done cd - From f62ddc47c412114ab860012a144807279e739943 Mon Sep 17 00:00:00 2001 From: gcranju Date: Fri, 29 Nov 2024 11:04:59 +0545 Subject: [PATCH 59/75] cluster connection specs change --- .../contracts/adapters/ClusterConnection.sol | 66 ++++--- .../lib/openzeppelin-contracts-upgradeable | 2 +- .../evm/test/adapters/ClusterConnection.t.sol | 175 +++--------------- 3 files changed, 75 insertions(+), 168 deletions(-) diff --git a/contracts/evm/contracts/adapters/ClusterConnection.sol b/contracts/evm/contracts/adapters/ClusterConnection.sol index a0eb7e43..2f333310 100644 --- a/contracts/evm/contracts/adapters/ClusterConnection.sol +++ b/contracts/evm/contracts/adapters/ClusterConnection.sol @@ -2,11 +2,14 @@ pragma solidity >=0.8.0; pragma abicoder v2; +import {console2 } from "forge-std/Test.sol"; + import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; import "@xcall/utils/Types.sol"; import "@xcall/contracts/xcall/interfaces/IConnection.sol"; import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol"; import "@iconfoundation/xcall-solidity-library/utils/RLPEncode.sol"; +import "@iconfoundation/xcall-solidity-library/utils/RLPEncode.sol"; contract ClusterConnection is Initializable, IConnection { @@ -26,7 +29,7 @@ contract ClusterConnection is Initializable, IConnection { uint8 private validatorsThreshold; event Message(string targetNetwork, uint256 sn, bytes _msg); - event ValidatorSetAdded(address[] _validator, uint8 _threshold); + event ValidatorSetAdded(bytes[] _validator, uint8 _threshold); modifier onlyRelayer() { require(msg.sender == this.relayer(), "OnlyRelayer"); @@ -48,11 +51,12 @@ contract ClusterConnection is Initializable, IConnection { return validators; } - function setValidators(address[] memory _validators, uint8 _threshold) external onlyAdmin { + function updateValidators(bytes[] memory _validators, uint8 _threshold) external onlyAdmin { delete validators; for (uint i = 0; i < _validators.length; i++) { - if(!isValidator(_validators[i]) && _validators[i] != address(0)) { - validators.push(_validators[i]); + address validators_address = publicKeyToAddress(_validators[i]); + if(!isValidator(validators_address) && validators_address != address(0)) { + validators.push(validators_address); } } require(validators.length >= _threshold, "Not enough validators"); @@ -143,10 +147,11 @@ contract ClusterConnection is Initializable, IConnection { bytes32 messageHash = getMessageHash(srcNetwork, _connSn, _msg); uint signerCount = 0; address[] memory collectedSigners = new address[](_signedMessages.length); + for (uint i = 0; i < _signedMessages.length; i++) { address signer = recoverSigner(messageHash, _signedMessages[i]); require(signer != address(0), "Invalid signature"); - if (!isValidatorProcessed(collectedSigners, signer)){ + if (!isValidatorProcessed(collectedSigners, signer) && existsInValidators(signer)){ collectedSigners[signerCount] = signer; signerCount++; } @@ -155,6 +160,13 @@ contract ClusterConnection is Initializable, IConnection { recvMessage(srcNetwork,_connSn,_msg); } + function existsInValidators(address signer) internal view returns (bool) { + for (uint i = 0; i < validators.length; i++){ + if (validators[i] == signer) return true; + } + return false; + } + function isValidatorProcessed(address[] memory processedSigners, address signer) public pure returns (bool) { for (uint i = 0; i < processedSigners.length; i++) { if (processedSigners[i] == signer) { @@ -178,25 +190,14 @@ contract ClusterConnection is Initializable, IConnection { v += 27; } require(v == 27 || v == 28, "Invalid signature 'v' value"); - return ecrecover(toEthSignedMessageHash(messageHash), v, r, s); - } - - function toEthSignedMessageHash(bytes32 _messageHash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); + return ecrecover(messageHash, v, r, s); } - - /** - @notice Sends the message to a xCall. - @param srcNetwork String ( Network Id ) - @param _connSn Integer ( connection message sn ) - @param _msg Bytes ( serialized bytes of Service Message ) - */ function recvMessage( string memory srcNetwork, uint256 _connSn, bytes calldata _msg - ) public onlyRelayer { + ) internal { require(!receipts[srcNetwork][_connSn], "Duplicate Message"); receipts[srcNetwork][_connSn] = true; ICallService(xCall).handleMessage(srcNetwork, _msg); @@ -232,11 +233,19 @@ contract ClusterConnection is Initializable, IConnection { } /** + @notice Set the address of the admin. + @param _address The address of the admin. + */ + function setAdmin(address _address) external onlyAdmin { + adminAddress = _address; + } + + /** @notice Set the address of the relayer. @param _address The address of the relayer. */ - function setAdmin(address _address) external onlyRelayer { - adminAddress = _address; + function setRelayer(address _address) external onlyAdmin { + relayerAddress = _address; } /** @@ -272,7 +281,22 @@ contract ClusterConnection is Initializable, IConnection { srcNetwork.encodeString(), _connSn.encodeUint(), _msg.encodeBytes() - ); + ).encodeList(); return keccak256(rlp); } + + function publicKeyToAddress(bytes memory publicKey) internal pure returns (address addr) { + require(publicKey.length == 65, "Invalid public key length"); + + bytes32 hash; + + assembly { + let publicKeyStart := add(publicKey, 0x20) + let destinationStart := add(publicKeyStart, 1) + hash := keccak256(destinationStart, 64) + } + + addr = address(uint160(uint256(hash))); + } + } diff --git a/contracts/evm/lib/openzeppelin-contracts-upgradeable b/contracts/evm/lib/openzeppelin-contracts-upgradeable index 625fb3c2..4ca003c9 160000 --- a/contracts/evm/lib/openzeppelin-contracts-upgradeable +++ b/contracts/evm/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 625fb3c2b2696f1747ba2e72d1e1113066e6c177 +Subproject commit 4ca003c9635d2c16756cf8c9db6760e2d3653dee diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 57e4754c..5a52bee4 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -133,98 +133,6 @@ contract ClusterConnectionTest is Test { vm.stopPrank(); } - function testRecvMessage() public { - bytes memory data = bytes("test"); - string memory iconDapp = NetworkAddress.networkAddress( - nidSource, - "0xa" - ); - Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( - iconDapp, - ParseAddress.toString(address(dappSource)), - 1, - Types.CALL_MESSAGE_TYPE, - data, - new string[](0) - ); - Types.CSMessage memory message = Types.CSMessage( - Types.CS_REQUEST, - request.encodeCSMessageRequestV2() - ); - - vm.startPrank(destination_relayer); - adapterTarget.recvMessage( - nidSource, - 1, - RLPEncodeStruct.encodeCSMessage(message) - ); - vm.stopPrank(); - } - - function testRecvMessageUnAuthorized() public { - bytes memory data = bytes("test"); - string memory iconDapp = NetworkAddress.networkAddress( - nidSource, - "0xa" - ); - Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( - iconDapp, - ParseAddress.toString(address(dappSource)), - 1, - Types.CALL_MESSAGE_TYPE, - data, - new string[](0) - ); - Types.CSMessage memory message = Types.CSMessage( - Types.CS_REQUEST, - request.encodeCSMessageRequestV2() - ); - - vm.startPrank(user); - vm.expectRevert("OnlyRelayer"); - adapterTarget.recvMessage( - nidSource, - 1, - RLPEncodeStruct.encodeCSMessage(message) - ); - vm.stopPrank(); - } - - function testRecvMessageDuplicateMsg() public { - bytes memory data = bytes("test"); - string memory iconDapp = NetworkAddress.networkAddress( - nidSource, - "0xa" - ); - Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( - iconDapp, - ParseAddress.toString(address(dappSource)), - 1, - Types.CALL_MESSAGE_TYPE, - data, - new string[](0) - ); - Types.CSMessage memory message = Types.CSMessage( - Types.CS_REQUEST, - request.encodeCSMessageRequestV2() - ); - - vm.startPrank(destination_relayer); - adapterTarget.recvMessage( - nidSource, - 1, - RLPEncodeStruct.encodeCSMessage(message) - ); - - vm.expectRevert("Duplicate Message"); - adapterTarget.recvMessage( - nidSource, - 1, - RLPEncodeStruct.encodeCSMessage(message) - ); - vm.stopPrank(); - } - function testRevertMessage() public { vm.startPrank(destination_relayer); vm.expectRevert("CallRequestNotFound"); @@ -284,9 +192,9 @@ contract ClusterConnectionTest is Test { vm.stopPrank(); assert(source_relayer.balance == 10 ether); - } + } - function testGetReceipt() public { + function testRecvMessageWithMultiSignatures() public { bytes memory data = bytes("test"); string memory iconDapp = NetworkAddress.networkAddress( nidSource, @@ -304,50 +212,19 @@ contract ClusterConnectionTest is Test { Types.CS_REQUEST, request.encodeCSMessageRequestV2() ); - - assert(adapterTarget.getReceipt(nidSource, 1) == false); - - vm.startPrank(destination_relayer); - adapterTarget.recvMessage( - nidSource, - 1, - RLPEncodeStruct.encodeCSMessage(message) - ); - vm.stopPrank(); - - assert(adapterTarget.getReceipt(nidSource, 1) == true); - } - - function testRecvMessageWithMultiSignature() public { - bytes memory data = bytes("test"); - string memory iconDapp = NetworkAddress.networkAddress( - nidSource, - "0xa" - ); - Types.CSMessageRequestV2 memory request = Types.CSMessageRequestV2( - iconDapp, - ParseAddress.toString(address(dappSource)), - 1, - Types.CALL_MESSAGE_TYPE, - data, - new string[](0) - ); - Types.CSMessage memory message = Types.CSMessage( - Types.CS_REQUEST, - request.encodeCSMessageRequestV2() - ); - uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); - uint256 pk2 = hexStringToUint256("47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a"); - uint256 pk3 = hexStringToUint256("59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); - uint256 pk4 = hexStringToUint256("2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"); + uint256 pk = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + uint256 pk2 = 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a; + uint256 pk3 = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + uint256 pk4 = 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6; bytes32 hash = getMessageHash(nidSource, 1, RLPEncodeStruct.encodeCSMessage(message)); vm.startPrank(owner); - address[] memory validators = new address[](4); - validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - validators[1] = address(0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65); - validators[2] = address(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); - validators[3] = address(0xa0Ee7A142d267C1f36714E4a8F75612F20a79720); - adapterTarget.setValidators(validators, 4); + bytes[] memory validators = new bytes[](4); + validators[0] = bytes(hex"048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5"); + validators[1] = bytes(hex"04bf6ee64a8d2fdc551ec8bb9ef862ef6b4bcb1805cdc520c3aa5866c0575fd3b514c5562c3caae7aec5cd6f144b57135c75b6f6cea059c3d08d1f39a9c227219d"); + validators[2] = bytes(hex"04ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4"); + validators[3] = bytes(hex"043255458e24278e31d5940f304b16300fdff3f6efd3e2a030b5818310ac67af45e28d057e6a332d07e0c5ab09d6947fd4eed1a646edbf224e2d2fec6f49f90abc"); + adapterTarget.updateValidators(validators, 4); + adapterTarget.listValidators(); vm.stopPrank(); vm.startPrank(destination_relayer); @@ -370,7 +247,10 @@ contract ClusterConnectionTest is Test { function signMessage(uint256 pk,bytes32 hash) private pure returns (bytes memory){ (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, hash); + address signer = vm.addr(pk); bytes memory signature = combineSignature(r,s,v); + + address recoverSigner=ecrecover(hash,v,r,s); return signature; } @@ -402,11 +282,14 @@ contract ClusterConnectionTest is Test { function testAddValidator() public { vm.startPrank(owner); - address[] memory validators = new address[](2); - validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - validators[1] = address(0x976EA74026E726554dB657fA54763abd0C3a0aa9); - adapterTarget.setValidators(validators, 2); - assertEq(2, adapterTarget.listValidators().length); + bytes[] memory validators = new bytes[](4); + validators[0] = bytes(hex"048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5"); + validators[1] = bytes(hex"04bf6ee64a8d2fdc551ec8bb9ef862ef6b4bcb1805cdc520c3aa5866c0575fd3b514c5562c3caae7aec5cd6f144b57135c75b6f6cea059c3d08d1f39a9c227219d"); + validators[2] = bytes(hex"04ba5734d8f7091719471e7f7ed6b9df170dc70cc661ca05e688601ad984f068b0d67351e5f06073092499336ab0839ef8a521afd334e53807205fa2f08eec74f4"); + validators[3] = bytes(hex"043255458e24278e31d5940f304b16300fdff3f6efd3e2a030b5818310ac67af45e28d057e6a332d07e0c5ab09d6947fd4eed1a646edbf224e2d2fec6f49f90abc"); + adapterTarget.updateValidators(validators, 4); + console2.log(adapterTarget.listValidators()[0]); + assertEq(4, adapterTarget.listValidators().length); vm.stopPrank(); } @@ -439,10 +322,10 @@ contract ClusterConnectionTest is Test { uint256 pk = hexStringToUint256("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); bytes32 hash = keccak256(RLPEncodeStruct.encodeCSMessage(message)); vm.startPrank(owner); - address[] memory validators = new address[](2); - validators[0] = address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - validators[1] = address(0x976EA74026E726554dB657fA54763abd0C3a0aa9); - adapterTarget.setValidators(validators, 2); + bytes[] memory validators = new bytes[](2); + validators[0] = bytes(hex"048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5"); + validators[1] = bytes(hex"04bf6ee64a8d2fdc551ec8bb9ef862ef6b4bcb1805cdc520c3aa5866c0575fd3b514c5562c3caae7aec5cd6f144b57135c75b6f6cea059c3d08d1f39a9c227219d"); + adapterTarget.updateValidators(validators, 2); vm.stopPrank(); vm.startPrank(destination_relayer); vm.expectRevert("Not enough valid signatures passed"); @@ -463,7 +346,7 @@ contract ClusterConnectionTest is Test { srcNetwork.encodeString(), _connSn.encodeUint(), _msg.encodeBytes() - ); + ).encodeList(); return keccak256(rlp); } } From c7305abfbd2b7142eab5805bad6fc0f77d097543 Mon Sep 17 00:00:00 2001 From: DeepakBomjan <44976635+DeepakBomjan@users.noreply.github.com> Date: Fri, 29 Nov 2024 11:25:20 +0545 Subject: [PATCH 60/75] ci: update rustc version to 1.81.0 --- .github/workflows/stellar-build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stellar-build-and-test.yml b/.github/workflows/stellar-build-and-test.yml index bd23e862..9c2cffe0 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 From f3cfbc3f4d12659723c1f22807edad5afaf82c8a Mon Sep 17 00:00:00 2001 From: gcranju <075bct064.ranju@pcampus.edu.np> Date: Fri, 30 Aug 2024 12:09:50 +0545 Subject: [PATCH 61/75] fix: sui-multisig docs --- contracts/sui/multisig/README.md | 153 +++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 contracts/sui/multisig/README.md diff --git a/contracts/sui/multisig/README.md b/contracts/sui/multisig/README.md new file mode 100644 index 00000000..98f71cb1 --- /dev/null +++ b/contracts/sui/multisig/README.md @@ -0,0 +1,153 @@ +# Multisig Contract on Sui + +This README provides an overview of the Multisig Contract on Sui, which serves as a signature manager rather than a traditional multisig contract. Unlike multisig implementations on EVM or CosmWasm, Sui has a built-in multisig mechanism accessible via the CLI. However, since using the CLI may not be practical for everyone, this contract simplifies multisig management, ensuring a secure and efficient way to handle multisig transactions. + +## Features + +- **Signature Management**: Add members to create a multisig address. The address remains consistent across contracts. +- **Tamper-Proof**: The contract is designed solely as a manager, ensuring no vulnerabilities even if tampered with. + +## Contract Overview + +The contract comprises several core functions essential for creating and managing multisig transactions: + +- **`create_multi_signature`**: Initializes a multisig instance with the required signatures and signers. +- **`create_proposal`**: Creates a proposal with the serialized transaction bytes. +- **`approve_proposal`**: Allows members to approve a transaction by submitting their signatures. +- **`get_execute_command`**: Retrieves the command to execute a multisig transaction once the threshold is met. + +### Function Signatures + +- `create_multi_signature(raw_signatures:&vector>, signers:&vector, threshold:u16)` +- `create_proposal(storage:&mut Storage,title:String,tx_bytes_64:String,multisig_address:address,ctx:&TxContext)` +- `approve_proposal(storage:&mut Storage, proposal_id:u64, raw_signature_64:String, ctx:&TxContext)` +- `get_execute_command(storage:&Storage, proposal_id:u64): String` + +## Step-by-Step Guide + +### 1. Publishing the Contract + +First, deploy the multisig contract to the Sui network. This contract will act as a manager for your multisig addresses and will not hold any funds or perform any transactions on its own. + +### 2. Adding Members and Creating a Multisig Address + +To create a multisig address: + +- **Register Wallet**: You'll need the public key, not the address, to register a wallet in Sui. Refer to the [Sui Cryptography Documentation](https://docs.sui.io/concepts/cryptography/transaction-auth/keys-addresses) for more details on keys and addresses. +- **Frontend Interaction**: Since the Sui wallet does not directly expose public keys, when you connect your wallet to the multisig frontend, it will fetch the public key for you. +- **Setup**: After obtaining public keys, register each wallet and set the threshold for the number of required signatures. + +### 3. Creating and Proposing Transactions + +To propose a transaction: + +- **Serialize Transaction**: Generate serialized bytes of the transaction you wish to execute from the multisig. +- **Handle Large Transactions**: If the transaction bytes are too large, save them to a file and create a proposal with the digest of the intent. This digest can be obtained by signing the transaction bytes using the Sui CLI. When signing and executing later, you must provide the original transaction bytes, not the digest. + ```bash + digest=$(sui keytool sign --data $result --address $active_address --json | jq '.digest') + echo $digest + ``` +- **Create Proposal**: Use the `create_proposal` function to initiate the multisig process by creating a proposal with the serialized transaction bytes. + + **Important Note:** Ensure to provide a gas object that is held by the multisig address when creating the proposal. This is crucial because the transaction will need to be executed by the multisig, and therefore it requires its own gas object to complete the transaction. + + +### 4. Approving a Transaction + +To approve a transaction: + +- **Sign with Member**: Each member of the multisig must sign the serialized transaction bytes. +- **Submit Signature**: Members submit their signatures by calling the `approve_proposal` function with their signature and the proposal id. + +### 5. Executing the Transaction + +Once the required number of signatures (as per the threshold) is collected: + +- **Retrieve Execute Command**: Call the `get_execute_command` function to get the command required to execute the signed transaction. + ```bash + sui client execute-signed-tx --tx-bytes ${ORIGINAL_TX_BYTES} --signatures + ``` +- **Execute via CLI**: Replace ${ORIGINAL_TX_BYTES} with the original transaction bytes. Run the command retrieved from the previous step on the Sui CLI to execute the multisig transaction. + +And that's it! The transaction will be signed by multiple members and executed successfully. + +## Frontend Integration Guide for Multisig Contract on Sui + +This guide is for the frontend development team to integrate the multisig contract functionalities into the user interface. The backend handles wallet registration and proposal creation, while the frontend focuses on the signing, approving, and executing stages. + +### Workflow Overview + +1. **Wallet Registration and Proposal Creation**: Handled by the backend team. +2. **Signing and Approving a Proposal**: Handled by the frontend. +3. **Retrieving the Execute Command**: Handled by the frontend and provided to the user for execution via the CLI. + +### 1. Signing and Approving a Proposal + +#### Retrieving and Displaying Proposals + +To enable users to view the current proposals: + +- **Retrieve Proposals**: The frontend should call the `get_proposals` function to fetch the list of proposals stored in the contract. + +- The function returns a reference to a table containing the proposals, which can be displayed in the frontend for users to view and interact with. + +```javascript +// Fetch proposals from the contract +const proposals = await getProposalsFunction(storage_id: storage_id); +``` + +#### Step 1: User Provides Transaction Bytes + +- The frontend should allow users to input or upload the serialized transaction bytes that need to be signed. + +#### Step 2: Signing the Transaction Bytes + +- Once the user provides the transaction bytes, the frontend should facilitate the signing process using the connected wallet. + +- You can use the following code snippet to sign the transaction bytes: + +```javascript +// Assume 'kp' is the keypair obtained from the connected wallet +const message = transactionBytes; // This is the serialized transaction bytes provided by the user +const signature = (await kp.signPersonalMessage(message)).signature; +``` + +#### Step 3: Calling the Approve Function + +- After signing the transaction bytes, the frontend should call the contract's `approve_proposal` function with the obtained signature. + +- The function call would look something like this: + +```javascript +// Approve proposal using the obtained signature +await approveProposalFunction({ + storage_id: storage_id, + proposalId: proposalId, + rawSignature64: signature, +}); +``` + +- Here, `proposalId` is the ID of the proposal, `rawSignature64` is the signature obtained from the signing process. + +### 2. Retrieving and Presenting the Execute Command + +#### Step 1: Calling the Get Execute Command Function + +- Once the necessary number of approvals (as per the threshold) is met, the frontend should call the contract's `get_execute_command` function to retrieve the execute command. + +- The function call could be structured as follows: + +```javascript +// Retrieve the execute command +const executeCommand = await getExecuteCommandFunction({ + storage_id: storage_id, + proposalId: proposalId +}); +``` +- Here, `proposalId` is the ID of the proposal. + +#### Step 2: Presenting the Execute Command to the User + +- The frontend should then display the execute command to the user (likely the proposal creator or designated executor) so that they can copy, fill the transaction bytes blank space and run it in their CLI to execute the multisig transaction. + +- You can provide a simple UI element like a copy button next to the command to facilitate easy copying. From 88db33f58ad996e6c3b0a399b7166cbd5166688b Mon Sep 17 00:00:00 2001 From: gcranju <134275268+gcranju@users.noreply.github.com> Date: Wed, 4 Sep 2024 18:20:36 +0545 Subject: [PATCH 62/75] fix: get proposals function (#357) Co-authored-by: gcranju <075bct064.ranju@pcampus.edu.np> --- contracts/sui/multisig/sources/multisig.move | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index 57459bd6..be04f15f 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -72,7 +72,7 @@ module multisig::multisig { threshold:u16 } - public struct Proposal has store{ + public struct Proposal has store,drop,copy{ id:u64, title:String, multisig_address:address, @@ -105,8 +105,16 @@ module multisig::multisig { public fun get_wallets(self:&Storage):&VecMap{ &self.wallets } - public fun get_proposals(self:&Storage):&Table{ - &self.proposals + + public fun get_proposals(self:&Storage):vector{ + let mut i = 1; + let mut proposals = vector::empty(); + while (i <= self.proposal_count){ + let proposal = self.proposals.borrow(i); + proposals.push_back(*proposal); + i=i+1; + }; + proposals } public struct AdminCap has key,store { id: UID From 6f4499686c163b18f51292f6cf74130f8b96a329 Mon Sep 17 00:00:00 2001 From: Itshyphen <075bct064.ranju@pcampus.edu.np> Date: Fri, 18 Oct 2024 11:09:39 +0545 Subject: [PATCH 63/75] fix: getter functions added in multisig --- contracts/sui/multisig/sources/multisig.move | 42 ++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index be04f15f..918ca662 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -15,6 +15,10 @@ module multisig::multisig { use sui::{ecdsa_k1::secp256k1_verify}; use sui::{ecdsa_r1::secp256r1_verify}; + //ERROR + const EAlreadyApproved: u64 = 10; + const EApprovalThresholdNotMet: u64 = 11; + const EOnlyMember: u64 = 12; /** signature schemes*/ const FlagED25519 :u8= 0x00; @@ -183,7 +187,7 @@ module multisig::multisig { entry fun create_proposal(storage:&mut Storage,title:String,tx_bytes_64:String,multisig_address:address,ctx:&TxContext){ let tx_bytes=base64::decode(&tx_bytes_64); let wallet=storage.wallets.get(&multisig_address); - assert!(only_member(wallet,ctx.sender())==true); + assert!(only_member(wallet,ctx.sender())==true, EOnlyMember); let is_digest=tx_bytes.length()==32; let proposal_id=get_proposal_id(storage); let proposal= Proposal{ @@ -203,7 +207,7 @@ module multisig::multisig { let raw_signature=base64::decode(&raw_signature_64); let proposal = storage.proposals.borrow(proposal_id); let wallet= storage.wallets.get(&proposal.multisig_address); - assert!(only_member(wallet,ctx.sender())==true); + assert!(only_member(wallet,ctx.sender())==true, EOnlyMember); let (index,pubkey)=get_pubkey(wallet,ctx.sender()); assert!(index!=0); assert!(verify_pubkey(&pubkey,&proposal.tx_data,&raw_signature,proposal.is_digest)==true); @@ -211,7 +215,7 @@ module multisig::multisig { proposal_id:proposal_id, sui_address:ctx.sender() }; - assert!(storage.votes.contains(vote_key)==false); + assert!(storage.votes.contains(vote_key)==false, EAlreadyApproved); storage.votes.add(vote_key, Vote{ signature:raw_signature, voter:ctx.sender() @@ -223,6 +227,36 @@ module multisig::multisig { event::emit(Executed {proposal_id:proposal_id,command:command}); } + public fun isProposalApproved(storage:&Storage,proposal_id:u64):bool{ + let proposal=storage.proposals.borrow(proposal_id); + let wallet=storage.wallets.get(&proposal.multisig_address); + let mut signatures:vector> = vector::empty(); + + let mut i=0; + while( i =wallet.threshold as u64 + } + + public fun hasMemberApproved(storage:&Storage,proposal_id:u64,ctx:&TxContext):bool{ + let key=VoteKey{ + proposal_id:proposal_id, + sui_address:ctx.sender() + }; + storage.votes.contains(key) + } + public fun get_execute_command(storage:&Storage,proposal_id:u64):String{ let proposal=storage.proposals.borrow(proposal_id); let wallet=storage.wallets.get(&proposal.multisig_address); @@ -243,6 +277,8 @@ module multisig::multisig { }; + assert!(signatures.length()>=wallet.threshold as u64, EApprovalThresholdNotMet); + let multisig= create_multi_signature(&signatures,&wallet.signers,wallet.threshold); let multisig_serialized_64= base64::encode(&serialize_multisig(&multisig)); From 3b248e02faf8287da76281b30d1e47aed998a3f1 Mon Sep 17 00:00:00 2001 From: gcranju Date: Fri, 29 Nov 2024 11:46:24 +0545 Subject: [PATCH 64/75] fix: multisig getter function added --- contracts/sui/multisig/sources/multisig.move | 36 ++++++++------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/contracts/sui/multisig/sources/multisig.move b/contracts/sui/multisig/sources/multisig.move index 918ca662..a21a5576 100644 --- a/contracts/sui/multisig/sources/multisig.move +++ b/contracts/sui/multisig/sources/multisig.move @@ -82,6 +82,7 @@ module multisig::multisig { multisig_address:address, tx_data:vector, is_digest:bool, + approved:bool } public struct Vote has store,drop{ @@ -102,10 +103,9 @@ module multisig::multisig { proposals:Table, votes:Table, proposal_count:u64, + } - - } public fun get_wallets(self:&Storage):&VecMap{ &self.wallets } @@ -196,7 +196,7 @@ module multisig::multisig { multisig_address:multisig_address, tx_data:tx_bytes, is_digest, - + approved:false }; storage.proposals.add(proposal_id,proposal); storage.wallet_proposals.borrow_mut(multisig_address).push_back(proposal_id); @@ -220,20 +220,10 @@ module multisig::multisig { signature:raw_signature, voter:ctx.sender() }); - } - entry fun execute_event(storage:&Storage,proposal_id:u64){ - let command= get_execute_command(storage,proposal_id); - event::emit(Executed {proposal_id:proposal_id,command:command}); - } - - public fun isProposalApproved(storage:&Storage,proposal_id:u64):bool{ - let proposal=storage.proposals.borrow(proposal_id); - let wallet=storage.wallets.get(&proposal.multisig_address); let mut signatures:vector> = vector::empty(); - let mut i=0; - while( i =wallet.threshold as u64 + + if (signatures.length()>=wallet.threshold as u64){ + storage.proposals.borrow_mut(proposal_id).approved=true; + } } - public fun hasMemberApproved(storage:&Storage,proposal_id:u64,ctx:&TxContext):bool{ + entry fun execute_event(storage:&Storage,proposal_id:u64){ + let command= get_execute_command(storage,proposal_id); + event::emit(Executed {proposal_id:proposal_id,command:command}); + } + + public fun has_member_voted(storage:&Storage,proposal_id:u64,member:address):bool{ let key=VoteKey{ proposal_id:proposal_id, - sui_address:ctx.sender() + sui_address:member }; storage.votes.contains(key) } @@ -354,10 +352,6 @@ module multisig::multisig { bitmap, multi_pubkey } - - - - } public fun serialize_multisig(sig:&MultiSignature):vector{ From fa371d6eeb5b9ad1530c64a75f85874e06a7f0d5 Mon Sep 17 00:00:00 2001 From: gcranju Date: Fri, 29 Nov 2024 12:15:48 +0545 Subject: [PATCH 65/75] fix: cluster connection test fix --- contracts/evm/test/adapters/ClusterConnection.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/evm/test/adapters/ClusterConnection.t.sol b/contracts/evm/test/adapters/ClusterConnection.t.sol index 5a52bee4..2612acab 100644 --- a/contracts/evm/test/adapters/ClusterConnection.t.sol +++ b/contracts/evm/test/adapters/ClusterConnection.t.sol @@ -106,14 +106,14 @@ contract ClusterConnectionTest is Test { } function testSetAdmin() public { - vm.prank(source_relayer); + vm.prank(owner); adapterSource.setAdmin(user); assertEq(adapterSource.admin(), user); } function testSetAdminUnauthorized() public { vm.prank(user); - vm.expectRevert("OnlyRelayer"); + vm.expectRevert("OnlyAdmin"); adapterSource.setAdmin(user); } @@ -328,10 +328,10 @@ contract ClusterConnectionTest is Test { adapterTarget.updateValidators(validators, 2); vm.stopPrank(); vm.startPrank(destination_relayer); - vm.expectRevert("Not enough valid signatures passed"); bytes[] memory signatures = new bytes[](2) ; signatures[0] = signMessage(pk,hash); signatures[1] = signMessage(pk,hash); + vm.expectRevert("Not enough valid signatures passed"); adapterTarget.recvMessageWithSignatures( nidSource, 1, From 2a357b9d614aa937ae65176e6beec7035714b91a Mon Sep 17 00:00:00 2001 From: gcranju <134275268+gcranju@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:51:32 +0545 Subject: [PATCH 66/75] fix: sui rlp upgrade (#417) * fix: sui-rlp upgrade compatible * fix: sui-rlp upgrade compatible --- .../sui/libs/sui_rlp/sources/encoder.move | 10 +++---- contracts/sui/libs/sui_rlp/sources/utils.move | 29 +++++++++++++++---- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index 5e2b93b1..1e63672b 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 af37a07d..53529c3d 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); From b547292090ac29c797fc6c4ceceefe0ff4980ef8 Mon Sep 17 00:00:00 2001 From: gcranju Date: Mon, 2 Dec 2024 15:37:39 +0545 Subject: [PATCH 67/75] fix: storage optimization in xcall --- .../soroban/contracts/xcall/src/contract.rs | 5 ++ .../soroban/contracts/xcall/src/storage.rs | 75 +++++++------------ 2 files changed, 31 insertions(+), 49 deletions(-) diff --git a/contracts/soroban/contracts/xcall/src/contract.rs b/contracts/soroban/contracts/xcall/src/contract.rs index 8d81f6ac..6c5affce 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 cfa43a98..8879c6dc 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) - } - + let res = e.storage().instance().get(&key).unwrap_or(false); 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)) } @@ -259,8 +241,8 @@ pub fn increment_last_request_id(e: &Env) -> u128 { pub fn save_success_response(e: &Env, sn: u128) { let key = StorageKey::SuccessfulResponses(sn); - e.storage().persistent().set(&key, &true); - extend_persistent(e, &key); + e.storage().instance().set(&key, &true); + extend_instance(e); } pub fn extend_instance(e: &Env) { @@ -269,14 +251,9 @@ pub fn extend_instance(e: &Env) { .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); -} - -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); } + From c9a169e65f6426282285f454bf86b9cc51df4c66 Mon Sep 17 00:00:00 2001 From: gcranju Date: Mon, 2 Dec 2024 15:58:38 +0545 Subject: [PATCH 68/75] fix: successful response to persistent --- contracts/soroban/contracts/xcall/src/storage.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs index 8879c6dc..2e5ab547 100644 --- a/contracts/soroban/contracts/xcall/src/storage.rs +++ b/contracts/soroban/contracts/xcall/src/storage.rs @@ -91,7 +91,7 @@ pub fn get_rollback(e: &Env, sequence_no: u128) -> Result bool { let key = StorageKey::SuccessfulResponses(sn); - let res = e.storage().instance().get(&key).unwrap_or(false); + let res = e.storage().persistent().get(&key).unwrap_or(false); res } @@ -241,8 +241,8 @@ pub fn increment_last_request_id(e: &Env) -> u128 { pub fn save_success_response(e: &Env, sn: u128) { let key = StorageKey::SuccessfulResponses(sn); - e.storage().instance().set(&key, &true); - extend_instance(e); + e.storage().persistent().set(&key, &true); + extend_persistent(e); } pub fn extend_instance(e: &Env) { @@ -251,6 +251,12 @@ pub fn extend_instance(e: &Env) { .extend_ttl(LEDGER_THRESHOLD_INSTANCE, LEDGER_BUMP_INSTANCE); } +pub fn extend_persistent(e: &Env) { + e.storage() + .persistent() + .extend_ttl(LEDGER_THRESHOLD_PERSISTENT, LEDGER_BUMP_PERSISTENT); +} + pub fn extend_temporary_request(e: &Env, key: &StorageKey) { e.storage() .temporary() From 5f7cb9c53ef30d94337a8e708a669c224e9108b7 Mon Sep 17 00:00:00 2001 From: gcranju Date: Mon, 2 Dec 2024 16:03:43 +0545 Subject: [PATCH 69/75] fix: successful response to persistent --- contracts/soroban/contracts/xcall/src/storage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs index 2e5ab547..1fb56add 100644 --- a/contracts/soroban/contracts/xcall/src/storage.rs +++ b/contracts/soroban/contracts/xcall/src/storage.rs @@ -242,7 +242,7 @@ pub fn increment_last_request_id(e: &Env) -> u128 { pub fn save_success_response(e: &Env, sn: u128) { let key = StorageKey::SuccessfulResponses(sn); e.storage().persistent().set(&key, &true); - extend_persistent(e); + extend_persistent(e, &key); } pub fn extend_instance(e: &Env) { @@ -251,10 +251,10 @@ pub fn extend_instance(e: &Env) { .extend_ttl(LEDGER_THRESHOLD_INSTANCE, LEDGER_BUMP_INSTANCE); } -pub fn extend_persistent(e: &Env) { +pub fn extend_persistent(e: &Env, key: &StorageKey) { e.storage() .persistent() - .extend_ttl(LEDGER_THRESHOLD_PERSISTENT, LEDGER_BUMP_PERSISTENT); + .extend_ttl(key, LEDGER_THRESHOLD_PERSISTENT, LEDGER_BUMP_PERSISTENT); } pub fn extend_temporary_request(e: &Env, key: &StorageKey) { From 6a866bc5f942a96f4c23b7775803044612f74b1f Mon Sep 17 00:00:00 2001 From: gcranju Date: Tue, 3 Dec 2024 12:31:56 +0545 Subject: [PATCH 70/75] fix: javascore settngs.gradle --- contracts/javascore/settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/javascore/settings.gradle b/contracts/javascore/settings.gradle index bfa80980..4f1da5a8 100644 --- a/contracts/javascore/settings.gradle +++ b/contracts/javascore/settings.gradle @@ -4,7 +4,7 @@ include( 'xcall', 'xcall-lib', 'centralized-connection', - 'cluster-connection' + 'cluster-connection', 'aggregator' ) From dc50331b72fcd31f68cda2c0725ec4246a8f44e3 Mon Sep 17 00:00:00 2001 From: gcranju Date: Tue, 3 Dec 2024 12:44:47 +0545 Subject: [PATCH 71/75] fix: stellar script --- scripts/optimize-stellar.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/optimize-stellar.sh b/scripts/optimize-stellar.sh index 691c6e4b..b527d71f 100755 --- a/scripts/optimize-stellar.sh +++ b/scripts/optimize-stellar.sh @@ -11,7 +11,7 @@ cargo build --target wasm32-unknown-unknown --release for WASM in $build_directory/*.wasm; do NAME=$(basename "$WASM" .wasm)${SUFFIX}.wasm echo "Optimizing $NAME ... $WASM" - /usr/local/bin/stellar2 contract optimize --wasm "$WASM" + stellar contract optimize --wasm "$WASM" done cd - From 7662f6c7bc76a4487e2ba858e20e5bc2009fafc2 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Wed, 4 Dec 2024 10:29:31 +0545 Subject: [PATCH 72/75] fix: remove unwanted code --- .../aggregator/src/main/java/relay/aggregator/Packet.java | 2 -- 1 file changed, 2 deletions(-) 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 0568de37..138ab2bb 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java @@ -66,8 +66,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; From 1cea9f33b177bf79c70764ea4741a63ed2dba7fb Mon Sep 17 00:00:00 2001 From: sherpalden Date: Wed, 4 Dec 2024 11:45:29 +0545 Subject: [PATCH 73/75] fix: do not included admin as relayers --- .../main/java/relay/aggregator/RelayAggregator.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) 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 ec475a99..7525d24d 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -50,7 +50,6 @@ public RelayAggregator(Address _admin) { if (admin.get() == null) { admin.set(_admin); signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD); - addRelayer(_admin); } } @@ -60,12 +59,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); } @@ -107,10 +100,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); } } From 9dfbb74a9e2a218a891b677752fc367995b55688 Mon Sep 17 00:00:00 2001 From: sherpalden Date: Wed, 4 Dec 2024 12:23:22 +0545 Subject: [PATCH 74/75] fix: create packet id from the hash of entire packet && update unit tests --- .../main/java/relay/aggregator/Packet.java | 15 +- .../relay/aggregator/RelayAggregator.java | 28 +- .../relay/aggregator/RelayAggregatorTest.java | 596 +++++++++--------- 3 files changed, 320 insertions(+), 319 deletions(-) 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 138ab2bb..16d600cf 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; @@ -75,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()); } /** @@ -171,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 7525d24d..440b8c19 100644 --- a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -41,9 +41,9 @@ 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 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) { @@ -119,8 +119,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; } @@ -139,7 +144,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 (packets.get(pktID) == null) { packets.set(pktID, pkt); @@ -162,7 +167,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(), @@ -177,8 +182,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(); @@ -197,7 +201,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); } @@ -233,7 +237,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() { @@ -262,7 +266,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); @@ -274,7 +278,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 0da3391f..4165d4e6 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,347 +20,342 @@ 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; class RelayAggregatorTest extends TestBase { - private final ServiceManager sm = getServiceManager(); + private final ServiceManager sm = getServiceManager(); - private KeyWallet admin; - private Account adminAc; + private KeyWallet admin; + private Account adminAc; - private KeyWallet relayerOne; - private Account relayerOneAc; + private KeyWallet relayerOne; + private Account relayerOneAc; - private KeyWallet relayerTwo; - private Account relayerTwoAc; + private KeyWallet relayerTwo; + private Account relayerTwoAc; - private KeyWallet relayerThree; - private Account relayerThreeAc; + private KeyWallet relayerThree; + private Account relayerThreeAc; - private KeyWallet relayerFour; - private Account relayerFourAc; + private KeyWallet relayerFour; + private Account relayerFourAc; - private Score aggregator; - private RelayAggregator aggregatorSpy; + private Score aggregator; + private RelayAggregator aggregatorSpy; - @BeforeEach - void setup() throws Exception { - admin = KeyWallet.create(); - adminAc = sm.getAccount(Address.fromString(admin.getAddress().toString())); + @BeforeEach + void setup() throws Exception { + admin = KeyWallet.create(); + adminAc = sm.getAccount(Address.fromString(admin.getAddress().toString())); - relayerOne = KeyWallet.create(); - relayerOneAc = sm.getAccount(Address.fromString(relayerOne.getAddress().toString())); + relayerOne = KeyWallet.create(); + relayerOneAc = sm.getAccount(Address.fromString(relayerOne.getAddress().toString())); - relayerTwo = KeyWallet.create(); - relayerTwoAc = sm.getAccount(Address.fromString(relayerTwo.getAddress().toString())); + relayerTwo = KeyWallet.create(); + relayerTwoAc = sm.getAccount(Address.fromString(relayerTwo.getAddress().toString())); - relayerThree = KeyWallet.create(); - relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); + relayerThree = KeyWallet.create(); + relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); - relayerFour = KeyWallet.create(); - relayerFourAc = sm.getAccount(Address.fromString(relayerFour.getAddress().toString())); + relayerFour = KeyWallet.create(); + relayerFourAc = sm.getAccount(Address.fromString(relayerFour.getAddress().toString())); - aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress()); + aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress()); - Address[] relayers = new Address[] { adminAc.getAddress(), relayerOneAc.getAddress(), relayerTwoAc.getAddress(), - relayerThreeAc.getAddress() }; + Address[] relayers = new Address[] { relayerOneAc.getAddress(), relayerTwoAc.getAddress(), + relayerThreeAc.getAddress() }; - aggregator.invoke(adminAc, "setRelayers", (Object) relayers, 2); + aggregator.invoke(adminAc, "setRelayers", (Object) relayers, 2); - aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); - aggregator.setInstance(aggregatorSpy); - } + aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); + aggregator.setInstance(aggregatorSpy); + } + + @Test + public void testSetAdmin() { + Account newAdminAc = sm.createAccount(); + aggregator.invoke(adminAc, "setAdmin", newAdminAc.getAddress()); + + Address newAdmin = (Address) aggregator.call("getAdmin"); + assertEquals(newAdminAc.getAddress(), newAdmin); + } + + @Test + public void testSetAdmin_unauthorized() { + Account normalAc = sm.createAccount(); + Account newAdminAc = sm.createAccount(); + + Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress()); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the admin", e.getMessage()); + } + + @Test + public void testSetSignatureThreshold() { + int threshold = 3; + aggregator.invoke(adminAc, "setSignatureThreshold", threshold); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); + } + + @Test + public void testSetSignatureThreshold_unauthorised() { + int threshold = 3; + + Executable action = () -> aggregator.invoke(relayerOneAc, + "setSignatureThreshold", threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the admin", + e.getMessage()); + } + + @Test + public void testSetRelayers() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + Integer threshold = 3; + aggregator.invoke(adminAc, "setRelayers", (Object) newRelayers, threshold); + + Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); - @Test - public void testSetAdmin() { - Address oldAdmin = (Address) aggregator.call("getAdmin"); + Address[] expectedRelayers = new Address[] { relayerThreeAc.getAddress(), + relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - Account newAdminAc = sm.createAccount(); - aggregator.invoke(adminAc, "setAdmin", newAdminAc.getAddress()); + HashSet
updatedRelayersSet = new HashSet
(); + for (Address rlr : updatedRelayers) { + updatedRelayersSet.add(rlr); + } - Address newAdmin = (Address) aggregator.call("getAdmin"); - assertEquals(newAdminAc.getAddress(), newAdmin); + HashSet
expectedRelayersSet = new HashSet
(); + for (Address rlr : expectedRelayers) { + expectedRelayersSet.add(rlr); + } + + assertEquals(expectedRelayersSet, updatedRelayersSet); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); + } - Address[] relayers = (Address[]) aggregator.call("getRelayers"); + @Test + public void testSetRelayers_unauthorized() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - boolean containsNewAdmin = Arrays.asList(relayers).contains(newAdmin); - boolean containsOldAdmin = Arrays.asList(relayers).contains(oldAdmin); + Integer threshold = 3; + Executable action = () -> aggregator.invoke(relayerOneAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertTrue(containsNewAdmin); - assertFalse(containsOldAdmin); - } + assertEquals("Reverted(0): Unauthorized: caller is not the admin", + e.getMessage()); - @Test - public void testSetAdmin_unauthorized() { - Account normalAc = sm.createAccount(); - Account newAdminAc = sm.createAccount(); + } - Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress()); - UserRevertedException e = assertThrows(UserRevertedException.class, action); + @Test + public void testSetRelayers_invalidThreshold() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); - } + Integer threshold = 5; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); - @Test - public void testSetSignatureThreshold() { - int threshold = 3; - aggregator.invoke(adminAc, "setSignatureThreshold", threshold); + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); - Integer result = (Integer) aggregator.call("getSignatureThreshold"); - assertEquals(threshold, result); - } + } - @Test - public void testSetSignatureThreshold_unauthorised() { - int threshold = 3; + @Test + public void testSetRelayers_invalidThresholdZero() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; - Executable action = () -> aggregator.invoke(relayerOneAc, - "setSignatureThreshold", threshold); - UserRevertedException e = assertThrows(UserRevertedException.class, action); + Integer threshold = 0; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", - e.getMessage()); - } + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); - @Test - public void testSetRelayers() { - Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), - relayerFiveAc.getAddress() }; + } - Integer threshold = 3; - aggregator.invoke(adminAc, "setRelayers", (Object) newRelayers, threshold); + @Test + public void testPacketSubmitted_true() 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); + + boolean submitted = (boolean) aggregator.call("packetSubmitted", + relayerOneAc.getAddress(), srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data); + assertEquals(submitted, true); + } - Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); + @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, srcHeight, dstNetwork, dstContractAddress, data); + assertEquals(submitted, false); + } - Address[] expectedRelayers = new Address[] { adminAc.getAddress(), relayerThreeAc.getAddress(), - relayerFourAc.getAddress(), - relayerFiveAc.getAddress() }; + @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); + + Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data); + byte[] pktID = pkt.getId(); + verify(aggregatorSpy).PacketRegistered(srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, + dstContractAddress, data); + verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); + } - HashSet
updatedRelayersSet = new HashSet
(); - for (Address rlr : updatedRelayers) { - updatedRelayersSet.add(rlr); + @Test + public void testSubmitPacket_thresholdReached() 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[] signOne = relayerOne.sign(dataHash); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, + 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] = signOne; + sigs[1] = signTwo; + + byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); + byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); + + assertArrayEquals(signOne, decodedSigs[0]); + assertArrayEquals(signTwo, decodedSigs[1]); + + verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, + srcSn, srcHeight, dstNetwork, + dstContractAddress, data, + encodedSigs); } - HashSet
expectedRelayersSet = new HashSet
(); - for (Address rlr : expectedRelayers) { - expectedRelayersSet.add(rlr); + @Test + public void testSubmitPacket_unauthorized() 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 }; + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerFour.sign(dataHash); + + Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", + srcNetwork, srcContractAddress, + srcSn, + srcHeight, dstNetwork, dstContractAddress, data, sign); + + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", + e.getMessage()); } - assertEquals(expectedRelayersSet, updatedRelayersSet); - - Integer result = (Integer) aggregator.call("getSignatureThreshold"); - assertEquals(threshold, result); - } - - @Test - public void testSetRelayers_unauthorized() { - Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), - relayerFiveAc.getAddress() }; - - Integer threshold = 3; - Executable action = () -> aggregator.invoke(relayerOneAc, "setRelayers", - (Object) newRelayers, threshold); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", - e.getMessage()); - - } - - @Test - public void testSetRelayers_invalidThreshold() { - Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), - relayerFiveAc.getAddress() }; - - Integer threshold = 5; - Executable action = () -> aggregator.invoke(adminAc, "setRelayers", - (Object) newRelayers, threshold); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", - e.getMessage()); - - } - - @Test - public void testSetRelayers_invalidThresholdZero() { - Account relayerFiveAc = sm.createAccount(); - Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), - relayerFiveAc.getAddress() }; - - Integer threshold = 0; - Executable action = () -> aggregator.invoke(adminAc, "setRelayers", - (Object) newRelayers, threshold); - UserRevertedException e = assertThrows(UserRevertedException.class, action); - - assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", - e.getMessage()); - - } - - @Test - public void testPacketSubmitted_true() 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); - - boolean submitted = (boolean) aggregator.call("packetSubmitted", - relayerOneAc.getAddress(), srcNetwork, - srcContractAddress, srcSn); - assertEquals(submitted, true); - } - - @Test - public void testPacketSubmitted_false() throws Exception { - String srcNetwork = "0x2.icon"; - BigInteger srcSn = BigInteger.ONE; - String srcContractAddress = "hxjuiod"; - - boolean submitted = (boolean) aggregator.call("packetSubmitted", - relayerOneAc.getAddress(), srcNetwork, - srcContractAddress, srcSn); - assertEquals(submitted, 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"; - 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[] 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, - dstContractAddress, - data, - signOne); - - byte[][] sigs = new byte[2][]; - sigs[0] = signAdmin; - sigs[1] = signOne; - - byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); - byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); - - assertArrayEquals(signAdmin, decodedSigs[0]); - assertArrayEquals(signOne, decodedSigs[1]); - - verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, - srcSn, srcHeight, dstNetwork, - dstContractAddress, data, - encodedSigs); - } - - @Test - public void testSubmitPacket_unauthorized() 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 }; - - byte[] dataHash = Context.hash("sha-256", data); - byte[] sign = relayerFour.sign(dataHash); - - Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", - srcNetwork, srcContractAddress, - srcSn, - srcHeight, dstNetwork, dstContractAddress, data, sign); - - UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", - e.getMessage()); - } - - @Test - public void testSubmitPacket_duplicate() 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); - - Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", - srcNetwork, srcContractAddress, srcSn, - srcHeight, dstNetwork, dstContractAddress, - data, sign); - ; - UserRevertedException e = assertThrows(UserRevertedException.class, action); - assertEquals("Reverted(0): Signature already exists", e.getMessage()); - } + @Test + public void testSubmitPacket_duplicate() 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); + + Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", + srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, dstContractAddress, + data, sign); + ; + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Signature already exists", e.getMessage()); + } } From c72a55083bdddda033e582ab36de338cc62d2529 Mon Sep 17 00:00:00 2001 From: gcranju <134275268+gcranju@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:59:14 +0545 Subject: [PATCH 75/75] fix: rlp issue resolved (#420) --- contracts/soroban/libs/soroban-rlp/src/decoder.rs | 3 +++ contracts/soroban/libs/soroban-rlp/src/utils.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/soroban/libs/soroban-rlp/src/decoder.rs b/contracts/soroban/libs/soroban-rlp/src/decoder.rs index 37b83405..9d05760d 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/soroban/libs/soroban-rlp/src/utils.rs b/contracts/soroban/libs/soroban-rlp/src/utils.rs index b359078c..403cac52 100644 --- a/contracts/soroban/libs/soroban-rlp/src/utils.rs +++ b/contracts/soroban/libs/soroban-rlp/src/utils.rs @@ -53,7 +53,7 @@ pub fn bytes_to_u64(bytes: Bytes) -> u64 { } pub fn u128_to_bytes(env: &Env, number: u128) -> Bytes { - let mut bytes: Bytes = Bytes::new(&env); + let mut bytes = bytes!(&env, 0x00); let mut i = 15; let mut leading_zero = true; while i >= 0 {