Skip to content

Commit

Permalink
feat: recover tokens (#1964)
Browse files Browse the repository at this point in the history
* feat: recover asset extrinsic

* tests: add UT

* wip: (de-)serialize RecoverAssets

* tests: improve transfer tranche tokens

* fix: u256 serialization

* ITs: add recover tokens

* tests: add schedule and cancel upgrade ITs
  • Loading branch information
wischli authored Aug 15, 2024
1 parent 0d29d17 commit e8f1efd
Show file tree
Hide file tree
Showing 7 changed files with 306 additions and 22 deletions.
39 changes: 39 additions & 0 deletions pallets/liquidity-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -1013,6 +1014,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<T>,
domain_address: DomainAddress,
incorrect_contract: [u8; 32],
asset: [u8; 32],
// NOTE: Solidity balance is `U256` per default
amount: U256,
) -> DispatchResult {
ensure_root(origin)?;

ensure!(
matches!(domain_address.domain(), Domain::EVM(_)),
Error::<T>::InvalidDomain
);

T::OutboundMessageHandler::handle(
T::TreasuryAccount::get(),
domain_address.domain(),
Message::RecoverAssets {
contract: incorrect_contract,
asset,
recipient: T::DomainAddressToAccountId::convert(domain_address).into(),
amount: amount.into(),
},
)?;

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down
27 changes: 23 additions & 4 deletions pallets/liquidity-pools/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -267,10 +266,10 @@ pub enum Message<BatchContent = BatchMessages> {
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
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion pallets/liquidity-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
99 changes: 99 additions & 0 deletions pallets/liquidity-pools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 |_| ALICE_EVM_LOCAL_ACCOUNT);
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: sp_core::U256::from(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::<Runtime>::InvalidDomain
);
});
}
}
}
30 changes: 16 additions & 14 deletions pallets/liquidity-pools/src/tests/inbound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.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 origin = EVM_DOMAIN_ADDRESS.domain().into_account();
let destination = OTHER_DOMAIN.into_account();
assert_ne!(destination, origin);
assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &origin), 0);

let destination = ALICE_EVM_DOMAIN_ADDRESS.domain().into_account();
assert_eq!(Tokens::balance(TRANCHE_CURRENCY, &destination), AMOUNT);
});
}
Expand Down
Loading

0 comments on commit e8f1efd

Please sign in to comment.