Skip to content

Commit

Permalink
feat: add update tranche hook extrinsic (#1954)
Browse files Browse the repository at this point in the history
* feat: add extrinsic

* tests: add IT
  • Loading branch information
wischli authored Aug 10, 2024
1 parent 1e318e2 commit ffed149
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 6 deletions.
7 changes: 7 additions & 0 deletions pallets/liquidity-pools/src/defensive_weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub trait WeightInfo {
fn update_tranche_token_metadata() -> Weight;
fn freeze_investor() -> Weight;
fn unfreeze_investor() -> Weight;
fn update_tranche_hook() -> Weight;
}

// NOTE: We use temporary weights here. `execute_epoch` is by far our heaviest
Expand Down Expand Up @@ -109,4 +110,10 @@ impl WeightInfo for () {
// Writes: MessageNonceStore, MessageQueue
RocksDbWeight::get().reads_writes(2, 2)
}

fn update_tranche_hook() -> Weight {
// Reads: Pool, Tranche, Permissions
// Writes: MessageNonceStore, MessageQueue
RocksDbWeight::get().reads_writes(3, 2)
}
}
52 changes: 52 additions & 0 deletions pallets/liquidity-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -961,6 +961,58 @@ pub mod pallet {

Ok(())
}

/// Notify the specified destination domain about a tranche hook address
/// update.
///
/// Origin: Pool admin
#[pallet::call_index(16)]
#[pallet::weight(T::WeightInfo::update_tranche_hook())]
pub fn update_tranche_hook(
origin: OriginFor<T>,
pool_id: T::PoolId,
tranche_id: T::TrancheId,
domain: Domain,
hook: [u8; 20],
) -> DispatchResult {
let who = ensure_signed(origin.clone())?;

ensure!(
T::PoolInspect::pool_exists(pool_id),
Error::<T>::PoolNotFound
);
ensure!(
T::PoolInspect::tranche_exists(pool_id, tranche_id),
Error::<T>::TrancheNotFound
);
ensure!(
T::Permission::has(
PermissionScope::Pool(pool_id),
who.clone(),
Role::PoolRole(PoolRole::PoolAdmin)
),
Error::<T>::NotPoolAdmin
);

let evm_chain_id = match domain {
Domain::EVM(id) => Ok(id),
_ => Err(Error::<T>::InvalidDomain),
}?;
let hook_32 =
T::DomainAddressToAccountId::convert(DomainAddress::EVM(evm_chain_id, hook)).into();

T::OutboundMessageHandler::handle(
who,
domain,
Message::UpdateTrancheHook {
pool_id: pool_id.into(),
tranche_id: tranche_id.into(),
hook: hook_32,
},
)?;

Ok(())
}
}

impl<T: Config> Pallet<T> {
Expand Down
4 changes: 3 additions & 1 deletion pallets/liquidity-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ pub const ALICE_EVM_LOCAL_ACCOUNT: AccountId = {
pub const CENTRIFUGE_DOMAIN_ADDRESS: DomainAddress = DomainAddress::Centrifuge(ALICE_32);
pub const CONTRACT_ACCOUNT: [u8; 20] = [1; 20];
pub const CONTRACT_ACCOUNT_ID: AccountId = AccountId::new([1; 32]);
pub const DOMAIN_HOOK_ADDRESS: [u8; 20] = [10u8; 20];
pub const DOMAIN_HOOK_ADDRESS_20: [u8; 20] = [10u8; 20];
pub const DOMAIN_HOOK_ADDRESS_32: [u8; 32] = [10u8; 32];
pub const EVM_DOMAIN_ADDRESS: DomainAddress = DomainAddress::EVM(CHAIN_ID, CONTRACT_ACCOUNT);
pub const EVM_DOMAIN: Domain = Domain::EVM(CHAIN_ID);
pub const AMOUNT: Balance = 100;
pub const CURRENCY_ID: CurrencyId = CurrencyId::ForeignAsset(1);
pub const POOL_CURRENCY_ID: CurrencyId = CurrencyId::LocalAsset(LocalAssetId(1));
Expand Down
166 changes: 163 additions & 3 deletions pallets/liquidity-pools/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ mod add_tranche {

fn config_mocks() {
let mut hook = [0; 32];
hook[0..20].copy_from_slice(&DOMAIN_HOOK_ADDRESS);
hook[0..20].copy_from_slice(&DOMAIN_HOOK_ADDRESS_20);
hook[20..28].copy_from_slice(&1u64.to_be_bytes());
hook[28..31].copy_from_slice(b"EVM");

Expand All @@ -487,10 +487,10 @@ mod add_tranche {
AssetRegistry::mock_metadata(|_| Some(util::default_metadata()));
Gateway::mock_get(move |domain| {
assert_eq!(domain, &EVM_DOMAIN_ADDRESS.domain());
Some(DOMAIN_HOOK_ADDRESS)
Some(DOMAIN_HOOK_ADDRESS_20)
});
DomainAddressToAccountId::mock_convert(move |domain| {
assert_eq!(domain, DomainAddress::EVM(CHAIN_ID, DOMAIN_HOOK_ADDRESS));
assert_eq!(domain, DomainAddress::EVM(CHAIN_ID, DOMAIN_HOOK_ADDRESS_20));
hook.clone().into()
});
Gateway::mock_handle(move |sender, destination, msg| {
Expand Down Expand Up @@ -1848,3 +1848,163 @@ mod unfreeze {
}
}
}

mod update_tranche_hook {
use super::*;

fn config_mocks() {
DomainAddressToAccountId::mock_convert(move |_| DOMAIN_HOOK_ADDRESS_32.into());
Permissions::mock_has(move |scope, who, role| {
assert!(matches!(scope, PermissionScope::Pool(POOL_ID)));
match role {
Role::PoolRole(PoolRole::PoolAdmin) => {
assert_eq!(who, ALICE);
true
}
_ => false,
}
});
Pools::mock_pool_exists(|_| true);
Pools::mock_tranche_exists(|_, _| true);
Gateway::mock_handle(|sender, destination, msg| {
assert_eq!(sender, ALICE);
assert_eq!(destination, EVM_DOMAIN);
assert_eq!(
msg,
Message::UpdateTrancheHook {
pool_id: POOL_ID,
tranche_id: TRANCHE_ID,
hook: DOMAIN_HOOK_ADDRESS_32
}
);
Ok(())
});
}

#[test]
fn success() {
System::externalities().execute_with(|| {
config_mocks();

assert_ok!(LiquidityPools::update_tranche_hook(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
));
});
}

mod erroring_out {
use cfg_types::domain_address::Domain;

use super::*;

#[test]
fn with_bad_origin_unsigned_none() {
System::externalities().execute_with(|| {
assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::none(),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
),
DispatchError::BadOrigin
);
});
}
#[test]
fn with_bad_origin_unsigned_root() {
System::externalities().execute_with(|| {
assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::root(),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
),
DispatchError::BadOrigin
);
});
}

#[test]
fn with_pool_dne() {
System::externalities().execute_with(|| {
config_mocks();
Pools::mock_pool_exists(|_| false);

assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
),
Error::<Runtime>::PoolNotFound
);
});
}

#[test]
fn with_tranche_dne() {
System::externalities().execute_with(|| {
config_mocks();
Pools::mock_tranche_exists(|_, _| false);

assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
),
Error::<Runtime>::TrancheNotFound
);
});
}

#[test]
fn with_origin_not_admin() {
System::externalities().execute_with(|| {
config_mocks();
Permissions::mock_has(|_, _, _| false);

assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
EVM_DOMAIN,
DOMAIN_HOOK_ADDRESS_20
),
Error::<Runtime>::NotPoolAdmin
);
});
}

#[test]
fn with_invalid_domain() {
System::externalities().execute_with(|| {
config_mocks();

assert_noop!(
LiquidityPools::update_tranche_hook(
RuntimeOrigin::signed(ALICE),
POOL_ID,
TRANCHE_ID,
Domain::Centrifuge,
DOMAIN_HOOK_ADDRESS_20
),
Error::<Runtime>::InvalidDomain
);
});
}
}
}
55 changes: 53 additions & 2 deletions runtime/integration-tests/src/cases/lp/pool_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ use sp_runtime::FixedPointNumber;

use crate::{
cases::lp::{
names, utils,
names,
names::POOL_A_T_1,
utils,
utils::{pool_a_tranche_1_id, Decoder},
LocalUSDC, EVM_DOMAIN_CHAIN_ID, LOCAL_RESTRICTION_MANAGER_ADDRESS, POOL_A, USDC,
LocalUSDC, EVM_DOMAIN, EVM_DOMAIN_CHAIN_ID, LOCAL_RESTRICTION_MANAGER_ADDRESS, POOL_A,
USDC,
},
config::Runtime,
env::{EnvEvmExtension, EvmEnv},
Expand Down Expand Up @@ -764,3 +767,51 @@ fn unfreeze_member<T: Runtime>() {
));
});
}

#[test_runtimes([centrifuge, development])]
fn update_tranche_hook<T: Runtime>() {
let new_hook: [u8; 20] = [1u8; 20];
let mut env = super::setup::<T, _>(|evm| {
super::setup_currencies(evm);
super::setup_pools(evm);
super::setup_tranches(evm);
super::setup_investment_currencies(evm);
super::setup_deploy_lps(evm);
});

env.state(|evm| {
let solidity = evm.deployed(names::RESTRICTION_MANAGER).address();
let rust = LOCAL_RESTRICTION_MANAGER_ADDRESS.into();
assert_eq!(
solidity, rust,
"Hook address changed, please change our stored value (right) to the new address (left)"
);
let hook_address = Decoder::<H160>::decode(
&evm.view(Keyring::Alice, POOL_A_T_1, "hook", None)
.unwrap()
.value,
);
assert_eq!(hook_address, solidity);
});

env.state_mut(|_| {
assert_ok!(pallet_liquidity_pools::Pallet::<T>::update_tranche_hook(
Keyring::Admin.as_origin(),
POOL_A,
pool_a_tranche_1_id::<T>(),
EVM_DOMAIN,
new_hook
));

utils::process_gateway_message::<T>(utils::verify_gateway_message_success::<T>);
});

env.state(|evm| {
let hook_address = Decoder::<H160>::decode(
&evm.view(Keyring::Alice, POOL_A_T_1, "hook", None)
.unwrap()
.value,
);
assert_eq!(hook_address, H160::from(new_hook));
});
}

0 comments on commit ffed149

Please sign in to comment.