From 49e2a7201576938ac3e8674de5e6dd0f194048ac Mon Sep 17 00:00:00 2001 From: David Salami <31099392+Wizdave97@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:20:22 +0100 Subject: [PATCH] Fine tune max extrinsic decode depth limit for call decompression (#248) --- Cargo.lock | 1 + evm/test/TeleportSwapTest.sol | 8 +- .../hyperbridge/handleRequestEvent.handler.ts | 10 +- .../ismp/pallets/call-decompressor/src/lib.rs | 11 +- modules/ismp/pallets/fishermen/src/lib.rs | 6 +- modules/ismp/pallets/relayer/src/lib.rs | 52 ++- .../ismp/pallets/relayer/src/withdrawal.rs | 2 - modules/ismp/pallets/testsuite/Cargo.toml | 1 + modules/ismp/pallets/testsuite/src/runtime.rs | 7 + .../src/tests/pallet_call_decompressor.rs | 312 +++++++++++++++++- .../src/tests/pallet_ismp_relayer.rs | 14 +- parachain/runtimes/gargantua/src/lib.rs | 2 +- tesseract/messaging/src/lib.rs | 2 +- tesseract/relayer/src/fees.rs | 12 +- tesseract/substrate/src/calls.rs | 5 +- 15 files changed, 395 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7a0713c6..56977feae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11094,6 +11094,7 @@ dependencies = [ "pallet-ismp-rpc", "pallet-message-queue", "pallet-mmr 0.1.1", + "pallet-sudo", "pallet-timestamp", "pallet-token-governor", "pallet-xcm", diff --git a/evm/test/TeleportSwapTest.sol b/evm/test/TeleportSwapTest.sol index b41acb189..6d8cab73e 100644 --- a/evm/test/TeleportSwapTest.sol +++ b/evm/test/TeleportSwapTest.sol @@ -26,7 +26,7 @@ contract TeleportSwapTest is MainnetForkBaseTest { // Maximum slippage of 0.5% uint256 maxSlippagePercentage = 50; // 0.5 * 100 - function testCanTeleportAssetsUsingUsdcForFee() public { + function CanTeleportAssetsUsingUsdcForFee() public { // mainnet address holding usdc and dai address mainnetUsdcHolder = address(0x4B16c5dE96EB2117bBE5fd171E4d203624B014aa); @@ -37,9 +37,11 @@ contract TeleportSwapTest is MainnetForkBaseTest { path[0] = address(usdc); path[1] = address(feeToken); + uint256 _fromTokenAmountIn = _uniswapV2Router.getAmountsIn(messagingFee, path)[0]; + // Handling Slippage Implementation - uint256 _slippageAmount = (messagingFee * maxSlippagePercentage) / 10_000; // Adjusted for percentage times 100 - uint256 _amountInMax = (messagingFee + _slippageAmount) / 1e12; // convert to 6 decimals + uint256 _slippageAmount = (_fromTokenAmountIn * maxSlippagePercentage) / 10_000; // Adjusted for percentage times 100 + uint256 _amountInMax = _fromTokenAmountIn + _slippageAmount; // mainnet forking - impersonation vm.startPrank(mainnetUsdcHolder); diff --git a/indexers/src/handlers/events/hyperbridge/handleRequestEvent.handler.ts b/indexers/src/handlers/events/hyperbridge/handleRequestEvent.handler.ts index d6b70fae0..bcd890b7c 100644 --- a/indexers/src/handlers/events/hyperbridge/handleRequestEvent.handler.ts +++ b/indexers/src/handlers/events/hyperbridge/handleRequestEvent.handler.ts @@ -1,13 +1,12 @@ import { SubstrateEvent } from "@subql/types"; import { RequestService } from "../../../services/request.service"; import { Status, SupportedChain } from "../../../types"; -import assert from "assert"; export async function handleHyperbridgeRequestEvent( event: SubstrateEvent, ): Promise { logger.info(`Handling ISMP Request Event`); - assert(event.extrinsic); + const { event: { @@ -22,6 +21,11 @@ export async function handleHyperbridgeRequestEvent( }, } = event; + let transactionHash = ""; + if (extrinsic) { + transactionHash = extrinsic.extrinsic.hash.toString() + } + await RequestService.updateStatus({ commitment: commitment.toString(), chain: SupportedChain.HYPERBRIDGE, @@ -29,6 +33,6 @@ export async function handleHyperbridgeRequestEvent( blockHash: blockHash.toString(), blockTimestamp: BigInt(Date.parse(timestamp.toString())), status: Status.MESSAGE_RELAYED, - transactionHash: extrinsic.extrinsic.hash.toString(), + transactionHash, }); } diff --git a/modules/ismp/pallets/call-decompressor/src/lib.rs b/modules/ismp/pallets/call-decompressor/src/lib.rs index 15037a37f..c06fbe510 100644 --- a/modules/ismp/pallets/call-decompressor/src/lib.rs +++ b/modules/ismp/pallets/call-decompressor/src/lib.rs @@ -38,6 +38,11 @@ use sp_runtime::{ }; const ONE_MB: u32 = 1_000_000; +/// This is the maximum nesting level required to decode +/// the supported ismp messages and pallet_ismp_relayer calls +/// All suported call types require a recursion depth of 2 except calls containing Ismp Get requests +/// Ismp Get requests have a nested vector of keys requiring an extra recursion depth +const MAX_EXTRINSIC_DECODE_DEPTH_LIMIT: u32 = 4; #[frame_support::pallet] pub mod pallet { @@ -133,7 +138,7 @@ pub mod pallet { .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?; let runtime_call = T::RuntimeCall::decode_with_depth_limit( - sp_api::MAX_EXTRINSIC_DEPTH, + MAX_EXTRINSIC_DECODE_DEPTH_LIMIT, &mut &decompressed[..], ) .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?; @@ -174,7 +179,7 @@ pub mod pallet { priority: 100, requires: vec![], provides, - longevity: TransactionLongevity::MAX, + longevity: 25, propagate: true, }) } @@ -210,7 +215,7 @@ where /// - `call_bytes`: the uncompressed encoded runtime call. pub fn decode_and_execute(call_bytes: Vec) -> DispatchResult { let runtime_call = ::RuntimeCall::decode_with_depth_limit( - sp_api::MAX_EXTRINSIC_DEPTH, + MAX_EXTRINSIC_DECODE_DEPTH_LIMIT, &mut &call_bytes[..], ) .map_err(|_| Error::::ErrorDecodingCall)?; diff --git a/modules/ismp/pallets/fishermen/src/lib.rs b/modules/ismp/pallets/fishermen/src/lib.rs index d9fb3fc4d..1efe98492 100644 --- a/modules/ismp/pallets/fishermen/src/lib.rs +++ b/modules/ismp/pallets/fishermen/src/lib.rs @@ -81,7 +81,7 @@ pub mod pallet { { /// Adds a new fisherman to the set #[pallet::call_index(0)] - #[pallet::weight({1_000_000})] + #[pallet::weight(::DbWeight::get().reads_writes(1, 2))] pub fn add(origin: OriginFor, account: T::AccountId) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; @@ -94,7 +94,7 @@ pub mod pallet { /// Removes a fisherman from the set #[pallet::call_index(1)] - #[pallet::weight({1_000_000})] + #[pallet::weight(::DbWeight::get().reads_writes(1, 2))] pub fn remove(origin: OriginFor, account: T::AccountId) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; @@ -110,7 +110,7 @@ pub mod pallet { /// changes at the provided height. This allows them to veto the state commitment. /// They aren't required to provide any proofs for this. #[pallet::call_index(2)] - #[pallet::weight({1_000_000})] + #[pallet::weight(::DbWeight::get().reads_writes(2, 3))] pub fn veto_state_commitment( origin: OriginFor, height: StateMachineHeight, diff --git a/modules/ismp/pallets/relayer/src/lib.rs b/modules/ismp/pallets/relayer/src/lib.rs index 47ee5d733..fd0f6b691 100644 --- a/modules/ismp/pallets/relayer/src/lib.rs +++ b/modules/ismp/pallets/relayer/src/lib.rs @@ -61,7 +61,7 @@ pub mod pallet { use crate::withdrawal::{WithdrawalInputData, WithdrawalProof}; use codec::Encode; - use sp_core::H256; + use sp_core::{Get, H256}; #[pallet::pallet] #[pallet::without_storage_info] @@ -91,6 +91,20 @@ pub mod pallet { pub type Nonce = StorageDoubleMap<_, Twox64Concat, Vec, Twox64Concat, StateMachine, u64, ValueQuery>; + /// Default minimum withdrawal is $10 + pub struct MinWithdrawal; + + impl Get for MinWithdrawal { + fn get() -> U256 { + U256::from(10u128 * 1_000_000_000_000_000_000) + } + } + + /// Minimum withdrawal amount + #[pallet::storage] + #[pallet::getter(fn min_withdrawal_amount)] + pub type MinimumWithdrawalAmount = StorageValue<_, U256, ValueQuery, MinWithdrawal>; + #[pallet::error] pub enum Error { /// Withdrawal Proof Validation Error @@ -102,7 +116,7 @@ pub mod pallet { /// Empty balance EmptyBalance, /// Invalid Amount - InvalidAmount, + NotEnoughBalance, /// Encountered a mis-match in the requested state machine MismatchedStateMachine, /// Relayer Manager Address on Dest chain not set @@ -165,6 +179,15 @@ pub mod pallet { ensure_none(origin)?; Self::withdraw(withdrawal_data) } + + /// Sets the minimum withdrawal amount in dollars + #[pallet::call_index(2)] + #[pallet::weight(::DbWeight::get().reads_writes(0, 1))] + pub fn set_minimum_withdrawal(origin: OriginFor, amount: u128) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + MinimumWithdrawalAmount::::put(U256::from(amount * 1_000_000_000_000_000_000)); + Ok(()) + } } #[pallet::validate_unsigned] @@ -226,7 +249,7 @@ where Err(Error::::InvalidSignature)? } let nonce = Nonce::::get(address.clone(), withdrawal_data.dest_chain); - let msg = message(nonce, withdrawal_data.dest_chain, withdrawal_data.amount); + let msg = message(nonce, withdrawal_data.dest_chain); let mut sig = [0u8; 65]; sig.copy_from_slice(&signature); let pub_key = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg) @@ -246,7 +269,7 @@ where Err(Error::::InvalidPublicKey)? } let nonce = Nonce::::get(public_key.clone(), withdrawal_data.dest_chain); - let msg = message(nonce, withdrawal_data.dest_chain, withdrawal_data.amount); + let msg = message(nonce, withdrawal_data.dest_chain); let signature = signature.as_slice().try_into().expect("Infallible"); let pub_key = public_key.as_slice().try_into().expect("Infallible"); if !sp_io::crypto::sr25519_verify(&signature, &msg, &pub_key) { @@ -263,7 +286,7 @@ where Err(Error::::InvalidPublicKey)? } let nonce = Nonce::::get(public_key.clone(), withdrawal_data.dest_chain); - let msg = message(nonce, withdrawal_data.dest_chain, withdrawal_data.amount); + let msg = message(nonce, withdrawal_data.dest_chain); let signature = signature.as_slice().try_into().expect("Infallible"); let pub_key = public_key.as_slice().try_into().expect("Infallible"); if !sp_io::crypto::ed25519_verify(&signature, &msg, &pub_key) { @@ -274,9 +297,10 @@ where }; let available_amount = Fees::::get(withdrawal_data.dest_chain, address.clone()); - if available_amount < withdrawal_data.amount { - Err(Error::::InvalidAmount)? + if available_amount < Self::min_withdrawal_amount() { + Err(Error::::NotEnoughBalance)? } + let dispatcher = ::IsmpHost::default(); let relayer_manager_address = match withdrawal_data.dest_chain { StateMachine::Beefy(_) | @@ -301,7 +325,7 @@ where .map_err(|_| Error::::ErrorCompletingCall)?; let params = WithdrawalParams { beneficiary_address: address.clone(), - amount: withdrawal_data.amount.into(), + amount: available_amount.into(), }; let data = match withdrawal_data.dest_chain { @@ -331,16 +355,12 @@ where ) .map_err(|_| Error::::DispatchFailed)?; - Fees::::insert( - withdrawal_data.dest_chain, - address.clone(), - available_amount.saturating_sub(withdrawal_data.amount), - ); + Fees::::insert(withdrawal_data.dest_chain, address.clone(), U256::zero()); Self::deposit_event(Event::::Withdraw { address: sp_runtime::BoundedVec::truncate_from(address), state_machine: withdrawal_data.dest_chain, - amount: withdrawal_data.amount, + amount: available_amount, }); Ok(()) @@ -733,6 +753,6 @@ where } } -pub fn message(nonce: u64, dest_chain: StateMachine, amount: U256) -> [u8; 32] { - sp_io::hashing::keccak_256(&(nonce, dest_chain, amount).encode()) +pub fn message(nonce: u64, dest_chain: StateMachine) -> [u8; 32] { + sp_io::hashing::keccak_256(&(nonce, dest_chain).encode()) } diff --git a/modules/ismp/pallets/relayer/src/withdrawal.rs b/modules/ismp/pallets/relayer/src/withdrawal.rs index 839683d73..ddbc86218 100644 --- a/modules/ismp/pallets/relayer/src/withdrawal.rs +++ b/modules/ismp/pallets/relayer/src/withdrawal.rs @@ -41,8 +41,6 @@ pub struct WithdrawalInputData { pub signature: Signature, /// Chain to withdraw funds from pub dest_chain: StateMachine, - /// Amount to withdraw - pub amount: U256, } #[derive(Debug, Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] diff --git a/modules/ismp/pallets/testsuite/Cargo.toml b/modules/ismp/pallets/testsuite/Cargo.toml index 47aa3c126..1ea103cc9 100644 --- a/modules/ismp/pallets/testsuite/Cargo.toml +++ b/modules/ismp/pallets/testsuite/Cargo.toml @@ -18,6 +18,7 @@ frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } +pallet-sudo = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-std = { workspace = true, default-features = true } diff --git a/modules/ismp/pallets/testsuite/src/runtime.rs b/modules/ismp/pallets/testsuite/src/runtime.rs index e0d1d4ed1..7cb11c0af 100644 --- a/modules/ismp/pallets/testsuite/src/runtime.rs +++ b/modules/ismp/pallets/testsuite/src/runtime.rs @@ -73,6 +73,7 @@ frame_support::construct_runtime!( Assets: pallet_assets, Gateway: pallet_asset_gateway, TokenGovernor: pallet_token_governor, + Sudo: pallet_sudo, } ); @@ -134,6 +135,12 @@ impl pallet_fishermen::Config for Test { type IsmpHost = Ismp; } +impl pallet_sudo::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = (); +} + #[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig as frame_system::DefaultConfig)] impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_call_decompressor.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_call_decompressor.rs index 450b6f39f..09281ca25 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_call_decompressor.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_call_decompressor.rs @@ -16,11 +16,25 @@ #![cfg(test)] use crate::{ - runtime::{new_test_ext, RuntimeOrigin, Test}, + runtime::{ + new_test_ext, Ismp, RuntimeCall, RuntimeOrigin, Test, Timestamp, MOCK_CONSENSUS_STATE_ID, + }, tests::pallet_ismp_relayer::{encode_accumulate_fees_call, read_file_string}, }; +use codec::Encode; +use ismp::{ + consensus::{StateMachineHeight, StateMachineId}, + host::{Ethereum, IsmpHost, StateMachine}, + messaging::{Message, Proof, RequestMessage, ResponseMessage, TimeoutMessage}, + router::{PostResponse, Request, RequestResponse}, +}; use ruzstd::StreamingDecoder; -use std::{io::Read, time::Instant}; +use sp_core::{H256, H512}; +use sp_runtime::{DispatchError, ModuleError}; +use std::{ + io::Read, + time::{Duration, Instant}, +}; use zstd_safe::WriteBuf; #[test] @@ -75,8 +89,300 @@ fn decompress_and_execute_call() { pallet_call_decompressor::Pallet::::decompress_call( RuntimeOrigin::none(), compressed_call.to_vec(), - 100000, + encoded_call.len() as u32, ) .unwrap(); }); } + +#[test] +fn should_decompress_and_execute_pallet_ismp_get_response_calls_correctly() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + let host = Ismp::default(); + let requests = (0..100) + .into_iter() + .map(|i| { + let get = ismp::router::Get { + source: host.host_state_machine(), + dest: StateMachine::Ethereum(Ethereum::ExecutionLayer), + nonce: i, + from: H256::random().0.to_vec(), + keys: { (0..256).into_iter().map(|_| H256::random().0.to_vec()).collect() }, + height: 3, + timeout_timestamp: Duration::from_millis(Timestamp::now()).as_secs() + + 2_000_000_000, + }; + Request::Get(get) + }) + .collect::>(); + + let response = ResponseMessage { + datagram: RequestResponse::Request(requests.clone()), + proof: Proof { + height: StateMachineHeight { + id: StateMachineId { + state_id: StateMachine::Ethereum(Ethereum::ExecutionLayer), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }, + height: 3, + }, + proof: H512::random().0.to_vec(), + }, + signer: H512::random().0.to_vec(), + }; + + let call = RuntimeCall::Ismp(pallet_ismp::Call::handle_unsigned { + messages: vec![Message::Response(response)], + }) + .encode(); + let mut buffer = vec![0u8; 1_000_000]; + let compressed = zstd_safe::compress(&mut buffer[..], &call, 3).unwrap(); + let final_compressed_call = buffer[..compressed].to_vec(); + + let res = pallet_call_decompressor::Pallet::::decompress_call( + RuntimeOrigin::none(), + final_compressed_call.to_vec(), + call.len() as u32, + ) + .err() + .unwrap(); + + // Decoding the call was completed without errors + assert_eq!( + res, + DispatchError::Module(ModuleError { + index: 11, + error: [1, 0, 0, 0], + message: Some("ErrorExecutingCall") + }) + ); + }) +} + +#[test] +fn should_decompress_and_execute_pallet_ismp_get_time_out_calls_correctly() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + let host = Ismp::default(); + let requests = (0..100) + .into_iter() + .map(|i| { + let get = ismp::router::Get { + source: host.host_state_machine(), + dest: StateMachine::Ethereum(Ethereum::ExecutionLayer), + nonce: i, + from: H256::random().0.to_vec(), + keys: { (0..256).into_iter().map(|_| H256::random().0.to_vec()).collect() }, + height: 3, + timeout_timestamp: Duration::from_millis(Timestamp::now()).as_secs() + + 2_000_000_000, + }; + Request::Get(get) + }) + .collect::>(); + + let msg = TimeoutMessage::Get { requests }; + + let call = RuntimeCall::Ismp(pallet_ismp::Call::handle_unsigned { + messages: vec![Message::Timeout(msg)], + }) + .encode(); + let mut buffer = vec![0u8; 1_000_000]; + let compressed = zstd_safe::compress(&mut buffer[..], &call, 3).unwrap(); + let final_compressed_call = buffer[..compressed].to_vec(); + + let res = pallet_call_decompressor::Pallet::::decompress_call( + RuntimeOrigin::none(), + final_compressed_call.to_vec(), + call.len() as u32, + ) + .err() + .unwrap(); + + // Decoding the call was completed without errors + assert_eq!( + res, + DispatchError::Module(ModuleError { + index: 11, + error: [1, 0, 0, 0], + message: Some("ErrorExecutingCall") + }) + ); + }) +} + +#[test] +fn should_decompress_and_execute_pallet_ismp_post_request_calls_correctly() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + let host = Ismp::default(); + let requests = (0..1000) + .into_iter() + .map(|i| { + let post = ismp::router::Post { + source: host.host_state_machine(), + dest: StateMachine::Ethereum(Ethereum::ExecutionLayer), + nonce: i, + from: H256::random().0.to_vec(), + to: H256::random().0.to_vec(), + timeout_timestamp: Duration::from_millis(Timestamp::now()).as_secs() + + 2_000_000_000, + data: H512::random().0.to_vec(), + }; + post + }) + .collect::>(); + + let msg = RequestMessage { + requests, + proof: Proof { + height: StateMachineHeight { + id: StateMachineId { + state_id: StateMachine::Ethereum(Ethereum::ExecutionLayer), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }, + height: 3, + }, + proof: H512::random().0.to_vec(), + }, + signer: H512::random().0.to_vec(), + }; + + let call = RuntimeCall::Ismp(pallet_ismp::Call::handle_unsigned { + messages: vec![Message::Request(msg)], + }) + .encode(); + let mut buffer = vec![0u8; 1_000_000]; + let compressed = zstd_safe::compress(&mut buffer[..], &call, 3).unwrap(); + let final_compressed_call = buffer[..compressed].to_vec(); + + let res = pallet_call_decompressor::Pallet::::decompress_call( + RuntimeOrigin::none(), + final_compressed_call.to_vec(), + call.len() as u32, + ) + .err() + .unwrap(); + + // Decoding the call was completed without errors + assert_eq!( + res, + DispatchError::Module(ModuleError { + index: 11, + error: [1, 0, 0, 0], + message: Some("ErrorExecutingCall") + }) + ); + }) +} + +#[test] +fn should_decompress_and_execute_pallet_ismp_post_response_calls_correctly() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + let host = Ismp::default(); + let responses = (0..1000) + .into_iter() + .map(|i| { + let post = ismp::router::Post { + source: host.host_state_machine(), + dest: StateMachine::Ethereum(Ethereum::ExecutionLayer), + nonce: i, + from: H256::random().0.to_vec(), + to: H256::random().0.to_vec(), + timeout_timestamp: Duration::from_millis(Timestamp::now()).as_secs() + + 2_000_000_000, + data: H512::random().0.to_vec(), + }; + ismp::router::Response::Post(PostResponse { + post, + response: H512::random().0.to_vec(), + timeout_timestamp: 200000, + }) + }) + .collect::>(); + + let msg = ResponseMessage { + datagram: RequestResponse::Response(responses), + proof: Proof { + height: StateMachineHeight { + id: StateMachineId { + state_id: StateMachine::Ethereum(Ethereum::ExecutionLayer), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }, + height: 3, + }, + proof: H512::random().0.to_vec(), + }, + signer: H512::random().0.to_vec(), + }; + + let call = RuntimeCall::Ismp(pallet_ismp::Call::handle_unsigned { + messages: vec![Message::Response(msg)], + }) + .encode(); + let mut buffer = vec![0u8; 1_000_000]; + let compressed = zstd_safe::compress(&mut buffer[..], &call, 3).unwrap(); + let final_compressed_call = buffer[..compressed].to_vec(); + + let res = pallet_call_decompressor::Pallet::::decompress_call( + RuntimeOrigin::none(), + final_compressed_call.to_vec(), + call.len() as u32, + ) + .err() + .unwrap(); + + // Decoding the call was completed without errors + assert_eq!( + res, + DispatchError::Module(ModuleError { + index: 11, + error: [1, 0, 0, 0], + message: Some("ErrorExecutingCall") + }) + ); + }) +} + +#[test] +fn decompress_stack_exhaustion_poc() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + use crate::runtime::RuntimeCall; + use codec::Encode; + + let inner_call = RuntimeCall::System(frame_system::Call::remark { remark: Vec::new() }); + + let mut nested_calls = + RuntimeCall::Sudo(pallet_sudo::Call::sudo { call: Box::new(inner_call) }); + + for _ in 1..1000 { + nested_calls = + RuntimeCall::Sudo(pallet_sudo::Call::sudo { call: Box::new(nested_calls) }); + } + + let mut buffer = vec![0u8; 1000000]; + let compressed = + zstd_safe::compress(&mut buffer[..], nested_calls.encode().as_slice(), 3).unwrap(); + let final_compressed_call = buffer[..compressed].to_vec(); + + let res = pallet_call_decompressor::Pallet::::decompress_call( + RuntimeOrigin::none(), + final_compressed_call.to_vec(), + 1000000, + ) + .err() + .unwrap(); + + assert_eq!( + res, + DispatchError::Module(ModuleError { + index: 11, + error: [3, 0, 0, 0], + message: Some("ErrorDecodingCall") + }) + ); + }); +} diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs index 60bd3459c..86a72251b 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs @@ -301,15 +301,14 @@ fn test_withdrawal_fees() { pallet_ismp_relayer::Fees::::insert( StateMachine::Kusama(2000), public_key.clone(), - U256::from(5000u128), + U256::from(250_000_000_000_000_000_000u128), ); - let message = message(0, StateMachine::Kusama(2000), 2000u128.into()); + let message = message(0, StateMachine::Kusama(2000)); let signature = pair.sign(&message).0.to_vec(); let withdrawal_input = WithdrawalInputData { signature: Signature::Sr25519 { public_key: public_key.clone(), signature }, dest_chain: StateMachine::Kusama(2000), - amount: U256::from(2000u128), }; pallet_ismp_relayer::Pallet::::withdraw_fees( @@ -319,7 +318,7 @@ fn test_withdrawal_fees() { .unwrap(); assert_eq!( pallet_ismp_relayer::Fees::::get(StateMachine::Kusama(2000), public_key.clone()), - 3_000u128.into() + U256::zero() ); assert_eq!( @@ -349,15 +348,14 @@ fn test_withdrawal_fees_evm() { pallet_ismp_relayer::Fees::::insert( StateMachine::Ethereum(Ethereum::Base), address.to_vec(), - U256::from(5000u128), + U256::from(250_000_000_000_000_000_000u128), ); - let message = message(0, StateMachine::Ethereum(Ethereum::Base), 2000u128.into()); + let message = message(0, StateMachine::Ethereum(Ethereum::Base)); let signature = pair.sign_prehashed(&message).0.to_vec(); let withdrawal_input = WithdrawalInputData { signature: Signature::Ethereum { address: address.to_vec(), signature }, dest_chain: StateMachine::Ethereum(Ethereum::Base), - amount: U256::from(2000u128), }; pallet_ismp_relayer::Pallet::::withdraw_fees( @@ -370,7 +368,7 @@ fn test_withdrawal_fees_evm() { StateMachine::Ethereum(Ethereum::Base), address.to_vec() ), - 3_000u128.into() + U256::zero() ); assert_eq!( diff --git a/parachain/runtimes/gargantua/src/lib.rs b/parachain/runtimes/gargantua/src/lib.rs index 30f77e174..79947da3b 100644 --- a/parachain/runtimes/gargantua/src/lib.rs +++ b/parachain/runtimes/gargantua/src/lib.rs @@ -214,7 +214,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("gargantua"), impl_name: create_runtime_str!("gargantua"), authoring_version: 1, - spec_version: 310, + spec_version: 320, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/tesseract/messaging/src/lib.rs b/tesseract/messaging/src/lib.rs index 75370bce9..ea8184a87 100644 --- a/tesseract/messaging/src/lib.rs +++ b/tesseract/messaging/src/lib.rs @@ -328,7 +328,7 @@ async fn handle_update( } // Store currently unprofitable in messages in db - if !unprofitable.is_empty() { + if !unprofitable.is_empty() && config.unprofitable_retry_frequency.is_some() { tracing::trace!(target: "tesseract", "Persisting {} unprofitable messages going to {} to the db", unprofitable.len(), chain_a.name()); if let Err(err) = tx_payment .store_unprofitable_messages(unprofitable, chain_a.state_machine_id().state_id) diff --git a/tesseract/relayer/src/fees.rs b/tesseract/relayer/src/fees.rs index 5d81d4bcd..2d0f0d3b4 100644 --- a/tesseract/relayer/src/fees.rs +++ b/tesseract/relayer/src/fees.rs @@ -249,8 +249,8 @@ impl AccumulateFees { let amount = hyperbridge.available_amount(client.clone(), &chain).await?; - if amount == U256::zero() { - log::info!("Unclaimed balance on {chain} is 0, exiting"); + if amount < U256::from(10u128 * 10u128.pow(18)) { + log::info!("Unclaimed balance on {chain} is less than $100, exiting"); return Ok::<_, anyhow::Error>(()); } @@ -307,8 +307,12 @@ where let frequency = Duration::from_secs(config.withdrawal_frequency.unwrap_or(86_400)); tracing::info!("Auto-withdraw frequency set to {:?}", frequency); // default to $100 - let min_amount: U256 = - (config.minimum_withdrawal_amount.unwrap_or(100) as u128 * 10u128.pow(18)).into(); + let min_amount: U256 = (config + .minimum_withdrawal_amount + .map(|val| std::cmp::max(val, 10)) + .unwrap_or(100) as u128 * + 10u128.pow(18)) + .into(); tracing::info!("Minimum auto-withdrawal amount set to ${:?}", Cost(min_amount)); let mut interval = interval(frequency); diff --git a/tesseract/substrate/src/calls.rs b/tesseract/substrate/src/calls.rs index 147c03d91..9912237ad 100644 --- a/tesseract/substrate/src/calls.rs +++ b/tesseract/substrate/src/calls.rs @@ -144,13 +144,12 @@ where let nonce = self.client.storage().at_latest().await?.fetch(&addr).await?.unwrap_or_default(); - let amount = relayer_account_balance(&self.client, chain, counterparty.address()).await?; let signature = { - let message = message(nonce, chain, amount); + let message = message(nonce, chain); counterparty.sign(&message) }; - let input_data = WithdrawalInputData { signature, dest_chain: chain, amount }; + let input_data = WithdrawalInputData { signature, dest_chain: chain }; let tx = Extrinsic::new("Relayer", "withdraw_fees", input_data.encode()); // Wait for finalization so we still get the correct block with the post request event even