Skip to content

Commit

Permalink
fix: DOT swap output less than existential deposit (#4062)
Browse files Browse the repository at this point in the history
* swap (output less than ED of DOT) test to bouncer

* fix lint

* throw error in case we observe BroadcastFailure

* fix lint

* Added timeout in case no good/bad event is received

* fix: use extrinsic success event for dot egress

* fix: increase bouncer timeout

* refactor: move out of deposit witnessing

* chore: some cosmetic changes

---------

Co-authored-by: Jamie Ford <Jamie@chainflip.io>
Co-authored-by: kylezs <zsembery.kyle@gmail.com>
  • Loading branch information
3 people authored Oct 19, 2023
1 parent 2768d3c commit abb0f48
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 62 deletions.
1 change: 1 addition & 0 deletions bouncer/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set -e
./commands/observe_block.ts 5
./commands/setup_vaults.ts
./commands/setup_swaps.ts
./tests/swap_less_than_existential_deposit_dot.ts
./tests/gaslimit_ccm.ts
./tests/all_concurrent_tests.ts
./tests/rotates_through_btc_swap.ts
Expand Down
106 changes: 106 additions & 0 deletions bouncer/tests/swap_less_than_existential_deposit_dot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/usr/bin/env -S pnpm tsx
import assert from 'assert';
import { getBalance } from '../shared/get_balance';
import { CcmDepositMetadata } from '../shared/new_swap';
import { SwapParams, requestNewSwap } from '../shared/perform_swap';
import { sendDot } from '../shared/send_dot';
import { sendErc20 } from '../shared/send_erc20';
import {
newAddress,
getChainflipApi,
observeEvent,
observeSwapScheduled,
observeCcmReceived,
observeBalanceIncrease,
getEthContractAddress,
observeBadEvents,
runWithTimeout,
} from '../shared/utils';

// This code is duplicated to allow us to specify a specific amount we want to swap
// and to wait for some specific events
export async function doPerformSwap(
{ sourceAsset, destAsset, destAddress, depositAddress, channelId }: SwapParams,
amount: string,
balanceIncrease: boolean,
tag = '',
messageMetadata?: CcmDepositMetadata,
) {
const oldBalance = await getBalance(destAsset, destAddress);

console.log(`${tag} Old balance: ${oldBalance}`);

const swapScheduledHandle = observeSwapScheduled(sourceAsset, destAsset, channelId);

const ccmEventEmitted = messageMetadata
? observeCcmReceived(sourceAsset, destAsset, destAddress, messageMetadata)
: Promise.resolve();

const contractAddress = getEthContractAddress('USDC');
await sendErc20(depositAddress, contractAddress, amount);

console.log(`${tag} Funded the address`);

await swapScheduledHandle;

console.log(`${tag} Waiting for balance to update`);

if (!balanceIncrease) {
const api = await getChainflipApi();
await observeEvent('polkadotBroadcaster:BroadcastSuccess', api);

const newBalance = await getBalance(destAsset, destAddress);

assert.strictEqual(newBalance, oldBalance, 'Balance should not have changed');
console.log(`${tag} Swap success! Balance (Same as before): ${newBalance}!`);
} else {
try {
const [newBalance] = await Promise.all([
observeBalanceIncrease(destAsset, destAddress, oldBalance),
ccmEventEmitted,
]);

console.log(`${tag} Swap success! New balance: ${newBalance}!`);
} catch (err) {
throw new Error(`${tag} ${err}`);
}
}
}

export async function swapLessThanED() {
console.log('=== Testing USDC -> DOT swaps obtaining less than ED ===');

let stopObserving = false;
const observingBadEvents = observeBadEvents(':BroadcastAborted', () => stopObserving);

// The initial price is 10USDC = 1DOT,
// we will swap only 5 USDC and check that the swap is completed successfully
const tag = `USDC -> DOT (less than ED)`;
const address = await newAddress('DOT', 'random seed');

console.log('Generated DOT address: ' + address);
const swapParams = await requestNewSwap('USDC', 'DOT', address, tag);
await doPerformSwap(swapParams, '5', false, tag);

await sendDot(address, '50');
console.log('Account funded, new balance: ' + (await getBalance('DOT', address)));

// We will then send some dot to the address and perform another swap with less than ED
const tag2 = `USDC -> DOT (to active account)`;
const swapParams2 = await requestNewSwap('USDC', 'DOT', address, tag2);
await doPerformSwap(swapParams2, '5', true, tag2);

stopObserving = true;
await observingBadEvents;

console.log('=== Test complete ===');
}

runWithTimeout(swapLessThanED(), 500000)
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(-1);
});
Binary file modified engine/metadata.polkadot.scale
Binary file not shown.
81 changes: 56 additions & 25 deletions engine/src/witness/dot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use super::common::{
};

// To generate the metadata file, use the subxt-cli tool (`cargo install subxt-cli`):
// subxt metadata --format=json --pallets Proxy,Balances,TransactionPayment --url
// subxt metadata --format=json --pallets Proxy,Balances,TransactionPayment,System --url
// wss://polkadot-rpc.dwellir.com:443 > metadata.polkadot.json.scale
#[subxt::subxt(runtime_metadata_path = "metadata.polkadot.scale")]
pub mod polkadot {}
Expand All @@ -49,10 +49,11 @@ pub enum EventWrapper {
ProxyAdded { delegator: AccountId32, delegatee: AccountId32 },
Transfer { to: AccountId32, from: AccountId32, amount: PolkadotBalance },
TransactionFeePaid { actual_fee: PolkadotBalance, tip: PolkadotBalance },
ExtrinsicSuccess,
}

use polkadot::{
balances::events::Transfer, proxy::events::ProxyAdded,
balances::events::Transfer, proxy::events::ProxyAdded, system::events::ExtrinsicSuccess,
transaction_payment::events::TransactionFeePaid,
};

Expand All @@ -76,6 +77,11 @@ pub fn filter_map_events(
event_details.as_event::<TransactionFeePaid>().unwrap().unwrap();
Some(EventWrapper::TransactionFeePaid { actual_fee, tip })
},
(ExtrinsicSuccess::PALLET, ExtrinsicSuccess::EVENT) => {
let ExtrinsicSuccess { .. } =
event_details.as_event::<ExtrinsicSuccess>().unwrap().unwrap();
Some(EventWrapper::ExtrinsicSuccess)
},
_ => None,
}
.map(|event| (event_details.phase(), event)),
Expand All @@ -88,7 +94,7 @@ pub fn filter_map_events(

pub async fn proxy_added_witnessing<ProcessCall, ProcessingFut>(
epoch: Vault<cf_chains::Polkadot, PolkadotAccountId, ()>,
header: Header<PolkadotBlockNumber, PolkadotHash, (Vec<(Phase, EventWrapper)>, BTreeSet<u32>)>,
header: Header<PolkadotBlockNumber, PolkadotHash, Vec<(Phase, EventWrapper)>>,
process_call: ProcessCall,
) -> (Vec<(Phase, EventWrapper)>, BTreeSet<u32>)
where
Expand All @@ -99,17 +105,16 @@ where
+ 'static,
ProcessingFut: Future<Output = ()> + Send + 'static,
{
let (events, mut broadcast_indices) = header.data;
let events = header.data;

let (vault_key_rotated_calls, mut proxy_added_broadcasts) =
let (vault_key_rotated_calls, proxy_added_broadcasts) =
proxy_addeds(header.index, &events, &epoch.info.1);
broadcast_indices.append(&mut proxy_added_broadcasts);

for call in vault_key_rotated_calls {
process_call(call, epoch.index).await;
}

(events, broadcast_indices)
(events, proxy_added_broadcasts)
}

#[allow(clippy::type_complexity)]
Expand All @@ -130,11 +135,15 @@ pub async fn process_egress<ProcessCall, ProcessingFut>(
+ 'static,
ProcessingFut: Future<Output = ()> + Send + 'static,
{
let ((events, broadcast_indices), monitored_egress_ids) = header.data;
let ((events, mut extrinsic_indices), monitored_egress_ids) = header.data;

// To guarantee witnessing egress, we are interested in all extrinsics that were successful
extrinsic_indices.extend(extrinsic_success_indices(&events));

let extrinsics = dot_client.extrinsics(header.hash).await;
let extrinsics: Vec<subxt::rpc::types::ChainBlockExtrinsic> =
dot_client.extrinsics(header.hash).await;

for (extrinsic_index, tx_fee) in transaction_fee_paids(&broadcast_indices, &events) {
for (extrinsic_index, tx_fee) in transaction_fee_paids(&extrinsic_indices, &events) {
let xt = extrinsics.get(extrinsic_index as usize).expect(
"We know this exists since we got
this index from the event, from the block we are querying.",
Expand Down Expand Up @@ -275,19 +284,29 @@ where

fn transaction_fee_paids(
indices: &BTreeSet<PolkadotExtrinsicIndex>,
events: &Vec<(Phase, EventWrapper)>,
events: &[(Phase, EventWrapper)],
) -> BTreeSet<(PolkadotExtrinsicIndex, PolkadotBalance)> {
let mut indices_with_fees = BTreeSet::new();
for (phase, wrapped_event) in events {
if let Phase::ApplyExtrinsic(extrinsic_index) = phase {
if indices.contains(extrinsic_index) {
if let EventWrapper::TransactionFeePaid { actual_fee, .. } = wrapped_event {
indices_with_fees.insert((*extrinsic_index, *actual_fee));
}
}
}
}
indices_with_fees
events
.iter()
.filter_map(|(phase, wrapped_event)| match (phase, wrapped_event) {
(
Phase::ApplyExtrinsic(extrinsic_index),
EventWrapper::TransactionFeePaid { actual_fee, .. },
) if indices.contains(extrinsic_index) => Some((*extrinsic_index, *actual_fee)),
_ => None,
})
.collect()
}

fn extrinsic_success_indices(events: &[(Phase, EventWrapper)]) -> BTreeSet<PolkadotExtrinsicIndex> {
events
.iter()
.filter_map(|(phase, wrapped_event)| match (phase, wrapped_event) {
(Phase::ApplyExtrinsic(extrinsic_index), EventWrapper::ExtrinsicSuccess) =>
Some(*extrinsic_index),
_ => None,
})
.collect()
}

fn proxy_addeds(
Expand Down Expand Up @@ -363,11 +382,23 @@ pub mod test {
(3u32, mock_tx_fee_paid(20000)),
]);

let (vault_key_rotated_calls, broadcast_indices) =
let (vault_key_rotated_calls, extrinsic_indices) =
proxy_addeds(20, &block_event_details, &our_vault);

assert_eq!(vault_key_rotated_calls.len(), 1);
assert_eq!(broadcast_indices.len(), 1);
assert!(broadcast_indices.contains(&our_proxy_added_index));
assert_eq!(extrinsic_indices.len(), 1);
assert!(extrinsic_indices.contains(&our_proxy_added_index));
}

#[tokio::test]
async fn test_extrinsic_success_filtering() {
let events = phase_and_events(vec![
(1u32, EventWrapper::ExtrinsicSuccess),
(2u32, mock_tx_fee_paid(20000)),
(2u32, EventWrapper::ExtrinsicSuccess),
(3u32, mock_tx_fee_paid(20000)),
]);

assert_eq!(extrinsic_success_indices(&events), BTreeSet::from([1, 2]));
}
}
Loading

0 comments on commit abb0f48

Please sign in to comment.