From ae751dc59cf161f13fd1d49ffd307735d252c31c Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Tue, 13 Aug 2024 21:31:54 +0200 Subject: [PATCH 1/7] feat: recover asset extrinsic --- pallets/liquidity-pools/src/lib.rs | 38 +++++++++++++++++++ .../submodules/liquidity-pools | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/pallets/liquidity-pools/src/lib.rs b/pallets/liquidity-pools/src/lib.rs index ad4cba72fe..75ed3fc8ba 100644 --- a/pallets/liquidity-pools/src/lib.rs +++ b/pallets/liquidity-pools/src/lib.rs @@ -1013,6 +1013,44 @@ pub mod pallet { Ok(()) } + + /// Initiate the recovery of assets which were sent to an incorrect + /// contract by the account represented by `domain_address`. + /// + /// NOTE: Asset and contract addresses in 32 bytes in order to support + /// future non-EVM chains. + /// + /// Origin: Root. + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::update_tranche_hook())] + pub fn recover_assets( + origin: OriginFor, + domain_address: DomainAddress, + incorrect_contract: [u8; 32], + asset: [u8; 32], + // NOTE: Solidity balance is `U256` per default + amount: sp_core::U256, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + matches!(domain_address.domain(), Domain::EVM(_)), + Error::::InvalidDomain + ); + + T::OutboundMessageHandler::handle( + T::TreasuryAccount::get(), + domain_address.domain(), + Message::RecoverAssets { + contract: incorrect_contract, + asset, + recipient: T::DomainAddressToAccountId::convert(domain_address).into(), + amount, + }, + )?; + + Ok(()) + } } impl Pallet { diff --git a/runtime/integration-tests/submodules/liquidity-pools b/runtime/integration-tests/submodules/liquidity-pools index 4301885b9a..6e8f1a29df 160000 --- a/runtime/integration-tests/submodules/liquidity-pools +++ b/runtime/integration-tests/submodules/liquidity-pools @@ -1 +1 @@ -Subproject commit 4301885b9a3b8ec36f3bda4b789daa5b115c006a +Subproject commit 6e8f1a29dff0d7cf5ff74285cfffadae8a8b303f From dcd875ef911dfc7a9f1c2adc86127b6a1e2bab8e Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Tue, 13 Aug 2024 21:32:57 +0200 Subject: [PATCH 2/7] tests: add UT --- pallets/liquidity-pools/src/mock.rs | 2 +- pallets/liquidity-pools/src/tests.rs | 99 ++++++++++++++++++++ pallets/liquidity-pools/src/tests/inbound.rs | 6 +- 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/pallets/liquidity-pools/src/mock.rs b/pallets/liquidity-pools/src/mock.rs index 4945190a4f..d676dd04ea 100644 --- a/pallets/liquidity-pools/src/mock.rs +++ b/pallets/liquidity-pools/src/mock.rs @@ -26,7 +26,7 @@ pub const CHAIN_ID: u64 = 1; pub const ALICE_32: [u8; 32] = [2; 32]; pub const ALICE: AccountId = AccountId::new(ALICE_32); pub const ALICE_ETH: [u8; 20] = [2; 20]; -pub const ALICE_EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::EVM(42, ALICE_ETH); +pub const ALICE_EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::EVM(CHAIN_ID, ALICE_ETH); // TODO(future): Can be removed after domain conversion refactor pub const ALICE_EVM_LOCAL_ACCOUNT: AccountId = { let mut arr = [0u8; 32]; diff --git a/pallets/liquidity-pools/src/tests.rs b/pallets/liquidity-pools/src/tests.rs index 4bb1353f83..090e0a33dc 100644 --- a/pallets/liquidity-pools/src/tests.rs +++ b/pallets/liquidity-pools/src/tests.rs @@ -2008,3 +2008,102 @@ mod update_tranche_hook { } } } + +mod recover_assets { + use super::*; + + const CONTRACT: [u8; 32] = [42; 32]; + const ASSET: [u8; 32] = [43; 32]; + + fn config_mocks() { + DomainAddressToAccountId::mock_convert(move |_| DOMAIN_HOOK_ADDRESS_32.into()); + Permissions::mock_has(|_, _, _| false); + Gateway::mock_handle(|sender, destination, msg| { + assert_eq!(sender, TreasuryAccount::get()); + assert_eq!(destination, EVM_DOMAIN); + assert_eq!( + msg, + Message::RecoverAssets { + contract: CONTRACT, + asset: ASSET, + recipient: ALICE_EVM_LOCAL_ACCOUNT.into(), + amount: AMOUNT.into(), + } + ); + Ok(()) + }); + } + + #[test] + fn success() { + System::externalities().execute_with(|| { + config_mocks(); + + assert_ok!(LiquidityPools::recover_assets( + RuntimeOrigin::root(), + ALICE_EVM_DOMAIN_ADDRESS, + CONTRACT, + ASSET, + AMOUNT.into(), + )); + }); + } + + mod erroring_out { + use super::*; + + #[test] + fn with_wrong_origin_none() { + System::externalities().execute_with(|| { + config_mocks(); + + assert_noop!( + LiquidityPools::recover_assets( + RuntimeOrigin::none(), + ALICE_EVM_DOMAIN_ADDRESS, + CONTRACT, + ASSET, + AMOUNT.into(), + ), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn with_wrong_origin_signed() { + System::externalities().execute_with(|| { + config_mocks(); + + assert_noop!( + LiquidityPools::recover_assets( + RuntimeOrigin::signed(ALICE.into()), + ALICE_EVM_DOMAIN_ADDRESS, + CONTRACT, + ASSET, + AMOUNT.into(), + ), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn with_wrong_domain() { + System::externalities().execute_with(|| { + config_mocks(); + + assert_noop!( + LiquidityPools::recover_assets( + RuntimeOrigin::root(), + DomainAddress::Centrifuge(ALICE.into()), + CONTRACT, + ASSET, + AMOUNT.into(), + ), + Error::::InvalidDomain + ); + }); + } + } +} diff --git a/pallets/liquidity-pools/src/tests/inbound.rs b/pallets/liquidity-pools/src/tests/inbound.rs index bc0dd61cbd..4c990a4445 100644 --- a/pallets/liquidity-pools/src/tests/inbound.rs +++ b/pallets/liquidity-pools/src/tests/inbound.rs @@ -169,6 +169,8 @@ mod handle_tranche_tokens_transfer { AMOUNT, ) .unwrap(); + let origin = EVM_DOMAIN_ADDRESS.domain().into_account(); + assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &origin), AMOUNT); assert_ok!(LiquidityPools::handle( EVM_DOMAIN_ADDRESS, @@ -181,10 +183,8 @@ mod handle_tranche_tokens_transfer { } )); - let origin = EVM_DOMAIN_ADDRESS.domain().into_account(); - assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &origin), 0); - let destination = ALICE_EVM_DOMAIN_ADDRESS.domain().into_account(); + assert_eq!(destination, origin); assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &destination), AMOUNT); }); } From 6713c03047fa80ab1d0619b1e8682e68cde508de Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Tue, 13 Aug 2024 21:40:11 +0200 Subject: [PATCH 3/7] wip: (de-)serialize RecoverAssets --- pallets/liquidity-pools/src/message.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pallets/liquidity-pools/src/message.rs b/pallets/liquidity-pools/src/message.rs index d981abfb16..00407eee3f 100644 --- a/pallets/liquidity-pools/src/message.rs +++ b/pallets/liquidity-pools/src/message.rs @@ -938,6 +938,19 @@ mod tests { ) } + #[test] + fn recover_assets() { + test_encode_decode_identity( + Message::RecoverAssets { + contract: default_address_32(), + asset: default_address_32(), + recipient: default_address_32(), + amount: Default::default(), + }, + "0de.....", + ) + } + #[test] fn batch_empty() { test_encode_decode_identity(Message::Batch(BatchMessages::default()), concat!("04")) From c75a38de95ca797494a4d3621b3dd826af42b4aa Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Wed, 14 Aug 2024 06:16:28 +0200 Subject: [PATCH 4/7] tests: improve transfer tranche tokens --- pallets/liquidity-pools/src/tests/inbound.rs | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/pallets/liquidity-pools/src/tests/inbound.rs b/pallets/liquidity-pools/src/tests/inbound.rs index 4c990a4445..d77ee70f14 100644 --- a/pallets/liquidity-pools/src/tests/inbound.rs +++ b/pallets/liquidity-pools/src/tests/inbound.rs @@ -85,6 +85,8 @@ mod handle_transfer { } mod handle_tranche_tokens_transfer { + use cfg_types::domain_address::Domain; + use super::*; fn config_mocks(receiver: DomainAddress) { @@ -143,48 +145,48 @@ mod handle_tranche_tokens_transfer { #[test] fn success_with_evm_domain() { + const OTHER_CHAIN_ID: u64 = CHAIN_ID + 1; + const OTHER_DOMAIN: Domain = Domain::EVM(OTHER_CHAIN_ID); + const OTHER_DOMAIN_ADDRESS_ALICE: DomainAddress = + DomainAddress::EVM(OTHER_CHAIN_ID, ALICE_ETH); + System::externalities().execute_with(|| { - config_mocks(ALICE_EVM_DOMAIN_ADDRESS); + config_mocks(OTHER_DOMAIN_ADDRESS_ALICE); TransferFilter::mock_check(|_| Ok(())); Gateway::mock_handle(|sender, destination, msg| { assert_eq!(sender, ALICE); - assert_eq!(destination, ALICE_EVM_DOMAIN_ADDRESS.domain()); + assert_eq!(destination, OTHER_DOMAIN); assert_eq!( msg, Message::TransferTrancheTokens { pool_id: POOL_ID, tranche_id: TRANCHE_ID, - domain: ALICE_EVM_DOMAIN_ADDRESS.domain().into(), - receiver: ALICE_EVM_DOMAIN_ADDRESS.address().into(), + domain: OTHER_DOMAIN.into(), + receiver: OTHER_DOMAIN_ADDRESS_ALICE.address().into(), amount: AMOUNT } ); Ok(()) }); - Tokens::mint_into( - TRANCHE_CURRENCY, - &EVM_DOMAIN_ADDRESS.domain().into_account(), - AMOUNT, - ) - .unwrap(); - let origin = EVM_DOMAIN_ADDRESS.domain().into_account(); - assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &origin), AMOUNT); + let origin = EVM_DOMAIN.into_account(); + Tokens::mint_into(TRANCHE_CURRENCY, &origin, AMOUNT).unwrap(); assert_ok!(LiquidityPools::handle( EVM_DOMAIN_ADDRESS, Message::TransferTrancheTokens { pool_id: POOL_ID, tranche_id: TRANCHE_ID, - domain: ALICE_EVM_DOMAIN_ADDRESS.domain().into(), + domain: OTHER_DOMAIN.into(), receiver: ALICE.into(), amount: AMOUNT } )); - let destination = ALICE_EVM_DOMAIN_ADDRESS.domain().into_account(); - assert_eq!(destination, origin); + let destination = OTHER_DOMAIN.into_account(); + assert_ne!(destination, origin); + assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &origin), 0); assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &destination), AMOUNT); }); } From 8f24889dfb72db047f978e08308f4a5c2299bf8d Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Wed, 14 Aug 2024 06:17:24 +0200 Subject: [PATCH 5/7] fix: u256 serialization --- pallets/liquidity-pools/src/lib.rs | 5 ++-- pallets/liquidity-pools/src/message.rs | 40 +++++++++++++++----------- pallets/liquidity-pools/src/tests.rs | 4 +-- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/pallets/liquidity-pools/src/lib.rs b/pallets/liquidity-pools/src/lib.rs index 75ed3fc8ba..31fde198d3 100644 --- a/pallets/liquidity-pools/src/lib.rs +++ b/pallets/liquidity-pools/src/lib.rs @@ -121,6 +121,7 @@ pub mod pallet { }; use frame_system::pallet_prelude::*; use parity_scale_codec::HasCompact; + use sp_core::U256; use sp_runtime::{traits::Zero, DispatchError}; use super::*; @@ -1029,7 +1030,7 @@ pub mod pallet { incorrect_contract: [u8; 32], asset: [u8; 32], // NOTE: Solidity balance is `U256` per default - amount: sp_core::U256, + amount: U256, ) -> DispatchResult { ensure_root(origin)?; @@ -1045,7 +1046,7 @@ pub mod pallet { contract: incorrect_contract, asset, recipient: T::DomainAddressToAccountId::convert(domain_address).into(), - amount, + amount: amount.into(), }, )?; diff --git a/pallets/liquidity-pools/src/message.rs b/pallets/liquidity-pools/src/message.rs index 00407eee3f..076c8f6250 100644 --- a/pallets/liquidity-pools/src/message.rs +++ b/pallets/liquidity-pools/src/message.rs @@ -15,7 +15,6 @@ use serde::{ ser::{Error as _, SerializeTuple}, Deserialize, Serialize, Serializer, }; -use sp_core::U256; use sp_runtime::{traits::ConstU32, DispatchError, DispatchResult}; use sp_std::{vec, vec::Vec}; @@ -267,10 +266,10 @@ pub enum Message { asset: Address, /// The user address which receives the recovered tokens recipient: Address, - /// The amount of tokens to recover + /// The amount of tokens to recover. /// - /// NOTE: Use `u256` as EVM balances are `u256`. - amount: U256, + /// NOTE: Represents `sp_core::U256` because EVM balances are `u256`. + amount: [u8; 32], }, // --- Gas service --- /// Updates the gas price which should cover transaction fees on Centrifuge @@ -903,6 +902,26 @@ mod tests { ) } + #[test] + fn recover_assets() { + let msg = Message::RecoverAssets { + contract: [2u8; 32], + asset: [1u8; 32], + recipient: [3u8; 32], + amount: (sp_core::U256::MAX - 1).into(), + }; + test_encode_decode_identity( + msg, + concat!( + "07", + "0202020202020202020202020202020202020202020202020202020202020202", + "0101010101010101010101010101010101010101010101010101010101010101", + "0303030303030303030303030303030303030303030303030303030303030303", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe", + ), + ); + } + #[test] fn update_tranche_token_metadata() { test_encode_decode_identity( @@ -938,19 +957,6 @@ mod tests { ) } - #[test] - fn recover_assets() { - test_encode_decode_identity( - Message::RecoverAssets { - contract: default_address_32(), - asset: default_address_32(), - recipient: default_address_32(), - amount: Default::default(), - }, - "0de.....", - ) - } - #[test] fn batch_empty() { test_encode_decode_identity(Message::Batch(BatchMessages::default()), concat!("04")) diff --git a/pallets/liquidity-pools/src/tests.rs b/pallets/liquidity-pools/src/tests.rs index 090e0a33dc..8654f41926 100644 --- a/pallets/liquidity-pools/src/tests.rs +++ b/pallets/liquidity-pools/src/tests.rs @@ -2016,7 +2016,7 @@ mod recover_assets { const ASSET: [u8; 32] = [43; 32]; fn config_mocks() { - DomainAddressToAccountId::mock_convert(move |_| DOMAIN_HOOK_ADDRESS_32.into()); + DomainAddressToAccountId::mock_convert(move |_| ALICE_EVM_LOCAL_ACCOUNT); Permissions::mock_has(|_, _, _| false); Gateway::mock_handle(|sender, destination, msg| { assert_eq!(sender, TreasuryAccount::get()); @@ -2027,7 +2027,7 @@ mod recover_assets { contract: CONTRACT, asset: ASSET, recipient: ALICE_EVM_LOCAL_ACCOUNT.into(), - amount: AMOUNT.into(), + amount: sp_core::U256::from(AMOUNT).into(), } ); Ok(()) From c81219830c93f24694122dd901a10455134f20c7 Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Wed, 14 Aug 2024 06:36:22 +0200 Subject: [PATCH 6/7] ITs: add recover tokens --- .../src/cases/lp/pool_management.rs | 92 ++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/runtime/integration-tests/src/cases/lp/pool_management.rs b/runtime/integration-tests/src/cases/lp/pool_management.rs index 48ada53f6f..8b3b62ac65 100644 --- a/runtime/integration-tests/src/cases/lp/pool_management.rs +++ b/runtime/integration-tests/src/cases/lp/pool_management.rs @@ -30,8 +30,8 @@ use crate::{ names::POOL_A_T_1, utils, utils::{pool_a_tranche_1_id, Decoder}, - LocalUSDC, EVM_DOMAIN, EVM_DOMAIN_CHAIN_ID, LOCAL_RESTRICTION_MANAGER_ADDRESS, POOL_A, - USDC, + LocalUSDC, DECIMALS_6, DEFAULT_BALANCE, EVM_DOMAIN, EVM_DOMAIN_CHAIN_ID, + LOCAL_RESTRICTION_MANAGER_ADDRESS, POOL_A, USDC, }, config::Runtime, env::{EnvEvmExtension, EvmEnv}, @@ -791,3 +791,91 @@ fn update_tranche_hook() { assert_eq!(hook_address, H160::from(new_hook)); }); } + +#[test] +fn tmp() { + recover_assets::() +} + +#[test_runtimes([development])] +fn recover_assets() { + let mut env = super::setup::(|evm| { + super::setup_currencies(evm); + }); + let investor = Keyring::Custom("WrongTransfer"); + let amount = DEFAULT_BALANCE * DECIMALS_6; + + // Transfer assets into wrong contract + let (token, wrong_contract) = env.state_mut(|evm| { + let wrong_contract = evm.deployed(names::POOL_MANAGER).address(); + let token = evm.deployed(names::USDC).address(); + + // Need to mint here instead of executing `transferAssets` because this would + // transfer to escrow instead of pool manager + evm.call( + Keyring::Admin, + Default::default(), + names::USDC, + "mint", + Some(&[ + Token::Address(wrong_contract.into()), + Token::Uint(sp_core::U256::from(amount)), + ]), + ) + .unwrap(); + + assert_eq!( + Decoder::::decode(&evm.view( + Keyring::Alice, + names::USDC, + "balanceOf", + Some(&[Token::Address(wrong_contract.into())]), + )), + amount + ); + assert_eq!( + Decoder::::decode(&evm.view( + Keyring::Alice, + names::USDC, + "balanceOf", + Some(&[Token::Address(investor.into())]), + )), + 0 + ); + + (token, wrong_contract) + }); + + env.state_mut(|_| { + assert_ok!(pallet_liquidity_pools::Pallet::::recover_assets( + ::RuntimeOrigin::root(), + DomainAddress::EVM(EVM_DOMAIN_CHAIN_ID, investor.into()), + utils::to_fixed_array(wrong_contract.as_bytes()), + utils::to_fixed_array(token.as_bytes()), + sp_core::U256::from(amount), + )); + + utils::process_gateway_message::(utils::verify_gateway_message_success::); + }); + + env.state(|evm| { + assert_eq!( + Decoder::::decode(&evm.view( + Keyring::Alice, + names::USDC, + "balanceOf", + Some(&[Token::Address(wrong_contract)]), + )), + 0 + ); + assert_eq!( + Decoder::::decode(&evm.view( + Keyring::Alice, + names::USDC, + "balanceOf", + Some(&[Token::Address(investor.into())]), + )), + amount + ); + }); +} From 67672b05cfc292db33e4c142cdac5ca31ed9acf4 Mon Sep 17 00:00:00 2001 From: William Freudenberger Date: Wed, 14 Aug 2024 06:36:41 +0200 Subject: [PATCH 7/7] tests: add schedule and cancel upgrade ITs --- .../src/cases/lp/pool_management.rs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/runtime/integration-tests/src/cases/lp/pool_management.rs b/runtime/integration-tests/src/cases/lp/pool_management.rs index 8b3b62ac65..7e0636ab25 100644 --- a/runtime/integration-tests/src/cases/lp/pool_management.rs +++ b/runtime/integration-tests/src/cases/lp/pool_management.rs @@ -879,3 +879,40 @@ fn recover_assets() { ); }); } + +#[test_runtimes([development])] +fn schedule_upgrade() { + let mut env = super::setup_full::(); + env.state_mut(|evm| { + assert_ok!(pallet_liquidity_pools::Pallet::::schedule_upgrade( + ::RuntimeOrigin::root(), + EVM_DOMAIN_CHAIN_ID, + evm.deployed(names::POOL_MANAGER).address().into() + )); + + utils::process_gateway_message::(utils::verify_gateway_message_success::); + }); +} + +#[test_runtimes([development])] +fn cancel_upgrade() { + let mut env = super::setup_full::(); + env.state_mut(|evm| { + assert_ok!(pallet_liquidity_pools::Pallet::::schedule_upgrade( + ::RuntimeOrigin::root(), + EVM_DOMAIN_CHAIN_ID, + evm.deployed(names::POOL_MANAGER).address().into() + )); + + utils::process_gateway_message::(utils::verify_gateway_message_success::); + }); + env.state_mut(|evm| { + assert_ok!(pallet_liquidity_pools::Pallet::::cancel_upgrade( + ::RuntimeOrigin::root(), + EVM_DOMAIN_CHAIN_ID, + evm.deployed(names::POOL_MANAGER).address().into() + )); + + utils::process_gateway_message::(utils::verify_gateway_message_success::); + }); +}