From f27536c110a51f48b88b6417aa43fd14f6d503c0 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 8 Nov 2023 17:58:19 +0100 Subject: [PATCH 1/4] Fix(xcc): Ensure near_withdraw comes after ft_transfer --- engine-types/src/parameters/xcc.rs | 8 +- .../src/contract_methods/evm_transactions.rs | 11 -- engine/src/contract_methods/xcc.rs | 55 ++++++++- engine/src/lib.rs | 13 +++ engine/src/xcc.rs | 104 ++++++++++++++++-- 5 files changed, 167 insertions(+), 24 deletions(-) diff --git a/engine-types/src/parameters/xcc.rs b/engine-types/src/parameters/xcc.rs index 20a82093b..93a09d36a 100644 --- a/engine-types/src/parameters/xcc.rs +++ b/engine-types/src/parameters/xcc.rs @@ -1,6 +1,6 @@ use crate::account_id::AccountId; use crate::borsh::{self, BorshDeserialize, BorshSerialize}; -use crate::types::Address; +use crate::types::{Address, Yocto}; #[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)] pub struct AddressVersionUpdateArgs { @@ -14,6 +14,12 @@ pub struct FundXccArgs { pub wnear_account_id: Option, } +#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)] +pub struct WithdrawWnearToRouterArgs { + pub target: Address, + pub amount: Yocto, +} + /// Type wrapper for version of router contracts. #[derive( Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, BorshDeserialize, BorshSerialize, diff --git a/engine/src/contract_methods/evm_transactions.rs b/engine/src/contract_methods/evm_transactions.rs index f773ee6d6..4041def0c 100644 --- a/engine/src/contract_methods/evm_transactions.rs +++ b/engine/src/contract_methods/evm_transactions.rs @@ -56,17 +56,6 @@ pub fn call( let current_account_id = env.current_account_id(); let predecessor_account_id = env.predecessor_account_id(); - // During the XCC flow the Engine will call itself to move wNEAR - // to the user's sub-account. We do not want this move to happen - // if prior promises in the flow have failed. - if current_account_id == predecessor_account_id { - let check_promise: Result<(), &[u8]> = match handler.promise_result_check() { - Some(true) | None => Ok(()), - Some(false) => Err(b"ERR_CALLBACK_OF_FAILED_PROMISE"), - }; - check_promise?; - } - let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( state, predecessor_address(&predecessor_account_id), diff --git a/engine/src/contract_methods/xcc.rs b/engine/src/contract_methods/xcc.rs index 2a265b93d..c71b5098b 100644 --- a/engine/src/contract_methods/xcc.rs +++ b/engine/src/contract_methods/xcc.rs @@ -1,17 +1,68 @@ use crate::{ - contract_methods::{require_owner_only, require_running, ContractError}, + contract_methods::{predecessor_address, require_owner_only, require_running, ContractError}, + engine::Engine, errors, hashchain::with_hashchain, state, xcc, }; +use aurora_engine_modexp::AuroraModExp; use aurora_engine_sdk::{ env::Env, io::{StorageIntermediate, IO}, promise::PromiseHandler, }; -use aurora_engine_types::{borsh::BorshSerialize, types::Address}; +use aurora_engine_types::{ + account_id::AccountId, borsh::BorshSerialize, format, + parameters::xcc::WithdrawWnearToRouterArgs, types::Address, +}; use function_name::named; +#[named] +pub fn withdraw_wnear_to_router( + io: I, + env: &E, + handler: &mut H, +) -> Result<(), ContractError> { + with_hashchain(io, env, function_name!(), |io| { + let state = state::get_state(&io)?; + require_running(&state)?; + env.assert_private_call()?; + let check_promise: Result<(), &[u8]> = match handler.promise_result_check() { + Some(true) | None => Ok(()), + Some(false) => Err(b"ERR_CALLBACK_OF_FAILED_PROMISE"), + }; + check_promise?; + let args: WithdrawWnearToRouterArgs = io.read_input_borsh()?; + let current_account_id = env.current_account_id(); + let recipient = AccountId::new(&format!( + "{}.{}", + args.target.encode(), + current_account_id.as_ref() + ))?; + let wnear_address = aurora_engine_precompiles::xcc::state::get_wnear_address(&io); + let mut engine: Engine<_, E, AuroraModExp> = Engine::new_with_state( + state, + predecessor_address(¤t_account_id), + current_account_id, + io, + env, + ); + let (result, ids) = xcc::withdraw_wnear_to_router( + &recipient, + args.amount, + wnear_address, + &mut engine, + handler, + )?; + if !result.status.is_ok() { + return Err(b"ERR_WITHDRAW_FAILED".into()); + } + let id = ids.last().ok_or(b"ERR_NO_PROMISE_CREATED")?; + handler.promise_return(*id); + Ok(()) + }) +} + #[named] pub fn factory_update(io: I, env: &E) -> Result<(), ContractError> { with_hashchain(io, env, function_name!(), |mut io| { diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 53c8c750e..89f82cc64 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -388,6 +388,19 @@ mod contract { .sdk_unwrap(); } + /// A private function (only callable by the contract itself) used as part of the XCC flow. + /// This function uses the exit to Near precompile to move wNear from Aurora to a user's + /// XCC account. + #[no_mangle] + pub extern "C" fn withdraw_wnear_to_router() { + let io = Runtime; + let env = Runtime; + let mut handler = Runtime; + contract_methods::xcc::withdraw_wnear_to_router(io, &env, &mut handler) + .map_err(ContractError::msg) + .sdk_unwrap(); + } + /// Mirror existing ERC-20 token on the main Aurora contract. /// Notice: It works if the SILO mode is on. #[no_mangle] diff --git a/engine/src/xcc.rs b/engine/src/xcc.rs index 6ca60f68f..9028bb320 100644 --- a/engine/src/xcc.rs +++ b/engine/src/xcc.rs @@ -1,11 +1,14 @@ +use crate::engine::{Engine, EngineResult}; use crate::errors::ERR_SERIALIZE; -use crate::parameters::{CallArgs, FunctionCallArgsV2}; -use aurora_engine_precompiles::xcc::state::{self, ERR_MISSING_WNEAR_ADDRESS}; +use crate::parameters::{CallArgs, FunctionCallArgsV2, SubmitResult}; +use aurora_engine_modexp::ModExpAlgorithm; +use aurora_engine_precompiles::xcc::state::ERR_MISSING_WNEAR_ADDRESS; use aurora_engine_sdk::env::Env; use aurora_engine_sdk::io::{StorageIntermediate, IO}; -use aurora_engine_sdk::promise::PromiseHandler; +use aurora_engine_sdk::promise::{PromiseHandler, PromiseId}; use aurora_engine_types::account_id::AccountId; use aurora_engine_types::borsh::BorshSerialize; +use aurora_engine_types::parameters::xcc::WithdrawWnearToRouterArgs; use aurora_engine_types::parameters::{PromiseAction, PromiseBatchAction, PromiseCreateArgs}; use aurora_engine_types::storage::{self, KeyPrefix}; use aurora_engine_types::types::{Address, NearGas, Yocto, ZERO_YOCTO}; @@ -248,15 +251,13 @@ pub fn handle_precompile_promise( let withdraw_id = if required_near == ZERO_YOCTO { setup_id } else { - let wnear_address = state::get_wnear_address(io); - let withdraw_call_args = CallArgs::V2(FunctionCallArgsV2 { - contract: wnear_address, - value: [0u8; 32], - input: withdraw_to_near_args(&promise.target_account_id, required_near), - }); + let withdraw_call_args = WithdrawWnearToRouterArgs { + target: sender, + amount: required_near, + }; let withdraw_call = PromiseCreateArgs { target_account_id: current_account_id.clone(), - method: "call".into(), + method: "withdraw_wnear_to_router".into(), args: withdraw_call_args.try_to_vec().unwrap(), attached_balance: ZERO_YOCTO, attached_gas: WITHDRAW_GAS, @@ -336,6 +337,23 @@ pub fn set_code_version_of_address(io: &mut I, address: &Address, version io.write_storage(&key, &value_bytes); } +pub fn withdraw_wnear_to_router( + recipient: &AccountId, + amount: Yocto, + wnear_address: Address, + engine: &mut Engine, + handler: &mut H, +) -> EngineResult<(SubmitResult, Vec)> { + let mut interceptor = PromiseInterceptor::new(handler); + let withdraw_call_args = CallArgs::V2(FunctionCallArgsV2 { + contract: wnear_address, + value: [0u8; 32], + input: withdraw_to_near_args(recipient, amount), + }); + let result = engine.call_with_args(withdraw_call_args, &mut interceptor)?; + Ok((result, interceptor.promises)) +} + #[derive(Debug, Clone, Copy)] pub enum FundXccError { InsufficientBalance, @@ -397,6 +415,72 @@ fn withdraw_to_near_args(recipient: &AccountId, amount: Yocto) -> Vec { [&WITHDRAW_TO_NEAR_SELECTOR, args.as_slice()].concat() } +/// A `PromiseHandler` that remembers all the `PromiseIds` it creates. +/// This is used to make a promise the return value of a function even +/// if the promise was not captured in the code where the handler is used. +/// For example, this can capture the promises created by the exit precompiles. +struct PromiseInterceptor<'a, H> { + inner: &'a mut H, + promises: Vec, +} + +impl<'a, H> PromiseInterceptor<'a, H> { + fn new(inner: &'a mut H) -> Self { + Self { + inner, + promises: Vec::new(), + } + } +} + +impl<'a, H: PromiseHandler> PromiseHandler for PromiseInterceptor<'a, H> { + type ReadOnly = H::ReadOnly; + + fn promise_results_count(&self) -> u64 { + self.inner.promise_results_count() + } + + fn promise_result(&self, index: u64) -> Option { + self.inner.promise_result(index) + } + + unsafe fn promise_create_call(&mut self, args: &PromiseCreateArgs) -> PromiseId { + let id = self.inner.promise_create_call(args); + self.promises.push(id); + id + } + + unsafe fn promise_create_and_combine(&mut self, args: &[PromiseCreateArgs]) -> PromiseId { + let id = self.inner.promise_create_and_combine(args); + self.promises.push(id); + id + } + + unsafe fn promise_attach_callback( + &mut self, + base: PromiseId, + callback: &PromiseCreateArgs, + ) -> PromiseId { + let id = self.inner.promise_attach_callback(base, callback); + self.promises.push(id); + id + } + + unsafe fn promise_create_batch(&mut self, args: &PromiseBatchAction) -> PromiseId { + let id = self.inner.promise_create_batch(args); + self.promises.push(id); + id + } + + fn promise_return(&mut self, promise: PromiseId) { + self.inner.promise_return(promise); + } + + fn read_only(&self) -> Self::ReadOnly { + self.inner.read_only() + } +} + #[cfg(test)] mod tests { use aurora_engine_types::{account_id::AccountId, types::Yocto, U256}; From a9043511c1bab695d685e8a03c16ebb9127adf45 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Wed, 8 Nov 2023 21:58:04 +0100 Subject: [PATCH 2/4] Add withdraw_wnear_to_router to standalone engine --- engine-standalone-storage/src/sync/mod.rs | 10 ++++++ engine-standalone-storage/src/sync/types.rs | 39 +++++++++++++++++++++ engine/src/contract_methods/xcc.rs | 15 ++++---- engine/src/xcc.rs | 17 ++++++--- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/engine-standalone-storage/src/sync/mod.rs b/engine-standalone-storage/src/sync/mod.rs index a16310537..cb23cfbdb 100644 --- a/engine-standalone-storage/src/sync/mod.rs +++ b/engine-standalone-storage/src/sync/mod.rs @@ -189,6 +189,10 @@ pub fn parse_transaction_kind( })?; TransactionKind::FactorySetWNearAddress(address) } + TransactionKindTag::WithdrawWnearToRouter => { + let args = xcc::WithdrawWnearToRouterArgs::try_from_slice(&bytes).map_err(f)?; + TransactionKind::WithdrawWnearToRouter(args) + } TransactionKindTag::SetUpgradeDelayBlocks => { let args = parameters::SetUpgradeDelayBlocksArgs::try_from_slice(&bytes).map_err(f)?; TransactionKind::SetUpgradeDelayBlocks(args) @@ -644,6 +648,12 @@ fn non_submit_execute( None } + TransactionKind::WithdrawWnearToRouter(_) => { + let mut handler = crate::promise::NoScheduler { promise_data }; + let result = contract_methods::xcc::withdraw_wnear_to_router(io, env, &mut handler)?; + + Some(TransactionExecutionResult::Submit(Ok(result))) + } TransactionKind::Unknown => None, // Not handled in this function; is handled by the general `execute_transaction` function TransactionKind::Submit(_) | TransactionKind::SubmitWithArgs(_) => unreachable!(), diff --git a/engine-standalone-storage/src/sync/types.rs b/engine-standalone-storage/src/sync/types.rs index 4d859b317..c30a3d5e3 100644 --- a/engine-standalone-storage/src/sync/types.rs +++ b/engine-standalone-storage/src/sync/types.rs @@ -5,6 +5,7 @@ use aurora_engine::xcc::{AddressVersionUpdateArgs, FundXccArgs}; use aurora_engine_transactions::{EthTransactionKind, NormalizedEthTransaction}; use aurora_engine_types::account_id::AccountId; use aurora_engine_types::parameters::silo; +use aurora_engine_types::parameters::xcc::WithdrawWnearToRouterArgs; use aurora_engine_types::types::Address; use aurora_engine_types::{ borsh::{self, BorshDeserialize, BorshSerialize}, @@ -146,6 +147,8 @@ pub enum TransactionKind { FactoryUpdateAddressVersion(AddressVersionUpdateArgs), FactorySetWNearAddress(Address), FundXccSubAccount(FundXccArgs), + /// Self-call used during XCC flow to move wNEAR tokens to user's XCC account + WithdrawWnearToRouter(WithdrawWnearToRouterArgs), /// Pause the contract PauseContract, /// Resume the contract @@ -374,6 +377,31 @@ impl TransactionKind { }, ) } + Self::WithdrawWnearToRouter(args) => { + let recipient = AccountId::new(&format!( + "{}.{}", + args.target.encode(), + engine_account.as_ref() + )) + .unwrap_or_else(|_| engine_account.clone()); + let wnear_address = storage + .with_engine_access(block_height, transaction_position, &[], |io| { + aurora_engine_precompiles::xcc::state::get_wnear_address(&io) + }) + .result; + let call_args = aurora_engine::xcc::withdraw_wnear_call_args( + &recipient, + args.amount, + wnear_address, + ); + Self::Call(call_args).eth_repr( + engine_account, + caller, + block_height, + transaction_position, + storage, + ) + } Self::Deposit(_) => Self::no_evm_execution("deposit"), Self::FtTransferCall(_) => Self::no_evm_execution("ft_transfer_call"), Self::FinishDeposit(_) => Self::no_evm_execution("finish_deposit"), @@ -550,6 +578,8 @@ pub enum TransactionKindTag { RemoveEntryFromWhitelist, #[strum(serialize = "mirror_erc20_token_callback")] MirrorErc20TokenCallback, + #[strum(serialize = "withdraw_wnear_to_router")] + WithdrawWnearToRouter, Unknown, } @@ -592,6 +622,7 @@ impl TransactionKind { Self::NewEngine(args) => args.try_to_vec().unwrap_or_default(), Self::FactoryUpdateAddressVersion(args) => args.try_to_vec().unwrap_or_default(), Self::FundXccSubAccount(args) => args.try_to_vec().unwrap_or_default(), + Self::WithdrawWnearToRouter(args) => args.try_to_vec().unwrap_or_default(), Self::PauseContract | Self::ResumeContract | Self::Unknown => Vec::new(), Self::SetKeyManager(args) => args.try_to_vec().unwrap_or_default(), Self::AddRelayerKey(args) | Self::RemoveRelayerKey(args) => { @@ -641,6 +672,7 @@ impl From<&TransactionKind> for TransactionKindTag { TransactionKind::FactoryUpdate(_) => Self::FactoryUpdate, TransactionKind::FactoryUpdateAddressVersion(_) => Self::FactoryUpdateAddressVersion, TransactionKind::FactorySetWNearAddress(_) => Self::FactorySetWNearAddress, + TransactionKind::WithdrawWnearToRouter(_) => Self::WithdrawWnearToRouter, TransactionKind::SetOwner(_) => Self::SetOwner, TransactionKind::SubmitWithArgs(_) => Self::SubmitWithArgs, TransactionKind::SetUpgradeDelayBlocks(_) => Self::SetUpgradeDelayBlocks, @@ -886,6 +918,7 @@ enum BorshableTransactionKind<'a> { SetWhitelistStatus(Cow<'a, silo::WhitelistStatusArgs>), SetEthConnectorContractAccount(Cow<'a, parameters::SetEthConnectorContractAccountArgs>), MirrorErc20TokenCallback(Cow<'a, parameters::MirrorErc20TokenArgs>), + WithdrawWnearToRouter(Cow<'a, WithdrawWnearToRouterArgs>), } impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { @@ -924,6 +957,9 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> { TransactionKind::FactorySetWNearAddress(address) => { Self::FactorySetWNearAddress(*address) } + TransactionKind::WithdrawWnearToRouter(x) => { + Self::WithdrawWnearToRouter(Cow::Borrowed(x)) + } TransactionKind::Unknown => Self::Unknown, TransactionKind::PausePrecompiles(x) => Self::PausePrecompiles(Cow::Borrowed(x)), TransactionKind::ResumePrecompiles(x) => Self::ResumePrecompiles(Cow::Borrowed(x)), @@ -1051,6 +1087,9 @@ impl<'a> TryFrom> for TransactionKind { BorshableTransactionKind::MirrorErc20TokenCallback(x) => { Ok(Self::MirrorErc20TokenCallback(x.into_owned())) } + BorshableTransactionKind::WithdrawWnearToRouter(x) => { + Ok(Self::WithdrawWnearToRouter(x.into_owned())) + } } } } diff --git a/engine/src/contract_methods/xcc.rs b/engine/src/contract_methods/xcc.rs index c71b5098b..21567e324 100644 --- a/engine/src/contract_methods/xcc.rs +++ b/engine/src/contract_methods/xcc.rs @@ -2,7 +2,7 @@ use crate::{ contract_methods::{predecessor_address, require_owner_only, require_running, ContractError}, engine::Engine, errors, - hashchain::with_hashchain, + hashchain::{with_hashchain, with_logs_hashchain}, state, xcc, }; use aurora_engine_modexp::AuroraModExp; @@ -12,8 +12,11 @@ use aurora_engine_sdk::{ promise::PromiseHandler, }; use aurora_engine_types::{ - account_id::AccountId, borsh::BorshSerialize, format, - parameters::xcc::WithdrawWnearToRouterArgs, types::Address, + account_id::AccountId, + borsh::BorshSerialize, + format, + parameters::{engine::SubmitResult, xcc::WithdrawWnearToRouterArgs}, + types::Address, }; use function_name::named; @@ -22,8 +25,8 @@ pub fn withdraw_wnear_to_router( io: I, env: &E, handler: &mut H, -) -> Result<(), ContractError> { - with_hashchain(io, env, function_name!(), |io| { +) -> Result { + with_logs_hashchain(io, env, function_name!(), |io| { let state = state::get_state(&io)?; require_running(&state)?; env.assert_private_call()?; @@ -59,7 +62,7 @@ pub fn withdraw_wnear_to_router( } let id = ids.last().ok_or(b"ERR_NO_PROMISE_CREATED")?; handler.promise_return(*id); - Ok(()) + Ok(result) }) } diff --git a/engine/src/xcc.rs b/engine/src/xcc.rs index 9028bb320..2fec0fd12 100644 --- a/engine/src/xcc.rs +++ b/engine/src/xcc.rs @@ -345,13 +345,22 @@ pub fn withdraw_wnear_to_router EngineResult<(SubmitResult, Vec)> { let mut interceptor = PromiseInterceptor::new(handler); - let withdraw_call_args = CallArgs::V2(FunctionCallArgsV2 { + let withdraw_call_args = withdraw_wnear_call_args(recipient, amount, wnear_address); + let result = engine.call_with_args(withdraw_call_args, &mut interceptor)?; + Ok((result, interceptor.promises)) +} + +#[must_use] +pub fn withdraw_wnear_call_args( + recipient: &AccountId, + amount: Yocto, + wnear_address: Address, +) -> CallArgs { + CallArgs::V2(FunctionCallArgsV2 { contract: wnear_address, value: [0u8; 32], input: withdraw_to_near_args(recipient, amount), - }); - let result = engine.call_with_args(withdraw_call_args, &mut interceptor)?; - Ok((result, interceptor.promises)) + }) } #[derive(Debug, Clone, Copy)] From 4bcddc91209cc83e581f6a6625d96a06c3508564 Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Fri, 10 Nov 2023 16:10:21 +0100 Subject: [PATCH 3/4] More consice failed promise check --- engine/src/contract_methods/xcc.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/engine/src/contract_methods/xcc.rs b/engine/src/contract_methods/xcc.rs index 21567e324..dd0b15242 100644 --- a/engine/src/contract_methods/xcc.rs +++ b/engine/src/contract_methods/xcc.rs @@ -30,11 +30,9 @@ pub fn withdraw_wnear_to_router( let state = state::get_state(&io)?; require_running(&state)?; env.assert_private_call()?; - let check_promise: Result<(), &[u8]> = match handler.promise_result_check() { - Some(true) | None => Ok(()), - Some(false) => Err(b"ERR_CALLBACK_OF_FAILED_PROMISE"), - }; - check_promise?; + if matches!(handler.promise_result_check(), Some(false)) { + return Err(b"ERR_CALLBACK_OF_FAILED_PROMISE".into()); + } let args: WithdrawWnearToRouterArgs = io.read_input_borsh()?; let current_account_id = env.current_account_id(); let recipient = AccountId::new(&format!( From d1ca7864665f811881392b21691288bb77f6983d Mon Sep 17 00:00:00 2001 From: Michael Birch Date: Fri, 10 Nov 2023 16:16:46 +0100 Subject: [PATCH 4/4] Fix: revert XCC precompile if the router account id is invalid --- engine-precompiles/src/xcc.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/engine-precompiles/src/xcc.rs b/engine-precompiles/src/xcc.rs index 874942438..8bfa7e660 100644 --- a/engine-precompiles/src/xcc.rs +++ b/engine-precompiles/src/xcc.rs @@ -52,6 +52,7 @@ mod consts { pub(super) const ERR_SERIALIZE: &str = "ERR_XCC_CALL_SERIALIZE"; pub(super) const ERR_STATIC: &str = "ERR_INVALID_IN_STATIC"; pub(super) const ERR_DELEGATE: &str = "ERR_INVALID_IN_DELEGATE"; + pub(super) const ERR_XCC_ACCOUNT_ID: &str = "ERR_FAILED_TO_CREATE_XCC_ACCOUNT_ID"; pub(super) const ROUTER_EXEC_NAME: &str = "execute"; pub(super) const ROUTER_SCHEDULE_NAME: &str = "schedule"; /// Solidity selector for the ERC-20 transferFrom function @@ -130,7 +131,7 @@ impl HandleBasedPrecompile for CrossContractCall { } let sender = context.caller; - let target_account_id = create_target_account_id(sender, self.engine_account_id.as_ref()); + let target_account_id = create_target_account_id(sender, self.engine_account_id.as_ref())?; let args = CrossContractCallArgs::try_from_slice(input) .map_err(|_| ExitError::Other(Cow::from(consts::ERR_INVALID_INPUT)))?; let (promise, attached_near) = match args { @@ -295,10 +296,13 @@ fn transfer_from_args(from: H160, to: H160, amount: U256) -> Vec { [&consts::TRANSFER_FROM_SELECTOR, args.as_slice()].concat() } -fn create_target_account_id(sender: H160, engine_account_id: &str) -> AccountId { +fn create_target_account_id( + sender: H160, + engine_account_id: &str, +) -> Result { format!("{}.{}", hex::encode(sender.as_bytes()), engine_account_id) .parse() - .unwrap_or_default() + .map_err(|_| revert_with_message(consts::ERR_XCC_ACCOUNT_ID)) } fn revert_with_message(message: &str) -> PrecompileFailure {