Skip to content

Commit

Permalink
Update code
Browse files Browse the repository at this point in the history
  • Loading branch information
niklasnatter committed Mar 7, 2025
1 parent 22165bd commit c70d2c1
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 84 deletions.
42 changes: 7 additions & 35 deletions bouncer/shared/evm_vault_swap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InternalAsset as Asset, approveVault, Asset as SCAsset, Chains } from '@chainflip/cli';
import { InternalAsset as Asset, Chains } from '@chainflip/cli';
import { HDNodeWallet } from 'ethers';
import { randomBytes } from 'crypto';
import BigNumber from 'bignumber.js';
Expand All @@ -19,6 +19,7 @@ import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from './new_swap'
import { getChainflipApi } from './utils/substrate';
import { ChannelRefundParameters } from './sol_vault_swap';
import { Logger } from './utils/logger';
import { approveErc20 } from './approve_erc20';

const erc20Assets: Asset[] = ['Flip', 'Usdc', 'Usdt', 'ArbUsdc'];

Expand Down Expand Up @@ -67,11 +68,12 @@ export async function executeEvmVaultSwap(

if (erc20Assets.includes(sourceAsset)) {
// Doing effectively infinite approvals to make sure it doesn't fail.
// eslint-disable-next-line @typescript-eslint/no-use-before-define
await approveEvmTokenVault(

await approveErc20(
logger,
sourceAsset,
getContractAddress(srcChain, 'VAULT'),
(BigInt(amountToFineAmount(amountToSwap, assetDecimals(sourceAsset))) * 100n).toString(),
evmWallet,
);
}

Expand Down Expand Up @@ -101,7 +103,7 @@ export async function executeEvmVaultSwap(
messageMetadata && {
message: messageMetadata.message as `0x${string}`,
gas_budget: messageMetadata.gasBudget,
ccm_additional_data: messageMetadata.ccmAdditionalData,
ccm_additional_data: messageMetadata.additionalData,
},
boostFeeBps ?? 0,
affiliateFees.map((fee) => ({
Expand All @@ -128,33 +130,3 @@ export async function executeEvmVaultSwap(

return receipt.transactionHash;
}

export async function approveEvmTokenVault(
sourceAsset: Asset,
amount: string,
wallet: HDNodeWallet,
) {
if (!erc20Assets.includes(sourceAsset)) {
throw new Error(`Unsupported asset, not an ERC20: ${sourceAsset}`);
}

const chain = chainFromAsset(sourceAsset as Asset);

await approveVault(
{
amount,
srcChain: chain,
srcAsset: stateChainAssetFromAsset(sourceAsset) as SCAsset,
},
{
signer: wallet,
network: 'localnet',
vaultContractAddress: getContractAddress(chain, 'VAULT'),
srcTokenContractAddress: getContractAddress(chain, sourceAsset),
},
// This is run with fresh addresses to prevent nonce issues
{
nonce: 0,
},
);
}
4 changes: 2 additions & 2 deletions bouncer/shared/new_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export async function newSwap(
destAddress: destinationAddress,
destChain: chainFromAsset(destAsset) as Chain,
ccmParams: messageMetadata && {
message: messageMetadata.message as `0x${string}`,
message: messageMetadata.message,
gasBudget: messageMetadata.gasBudget.toString(),
ccmAdditionalData: messageMetadata.ccmAdditionalData as `0x${string}`,
additionalData: messageMetadata.additionalData,
},
commissionBps: brokerCommissionBps,
maxBoostFeeBps: boostFeeBps,
Expand Down
2 changes: 1 addition & 1 deletion bouncer/shared/perform_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export async function requestNewSwap(
(messageMetadata.message === '0x' ? '' : messageMetadata.message) &&
event.data.channelMetadata.gasBudget.replace(/,/g, '') === messageMetadata.gasBudget &&
event.data.channelMetadata.ccmAdditionalData ===
(messageMetadata.ccmAdditionalData === '0x' ? '' : messageMetadata.ccmAdditionalData)
(messageMetadata.additionalData === '0x' ? '' : messageMetadata.additionalData)
: event.data.channelMetadata === null;

return destAddressMatches && destAssetMatches && sourceAssetMatches && ccmMetadataMatches;
Expand Down
11 changes: 2 additions & 9 deletions bouncer/shared/send_evm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Web3 from 'web3';
import { approveVault } from '@chainflip/cli';
import {
Chain,
amountToFineAmount,
Expand All @@ -18,10 +17,7 @@ const nextEvmNonce: { [key in 'Ethereum' | 'Arbitrum']: number | undefined } = {
Arbitrum: undefined,
};

export async function getNextEvmNonce(
chain: Chain,
callback?: (nextNonce: number) => ReturnType<typeof approveVault>,
): Promise<number> {
export async function getNextEvmNonce(chain: Chain): Promise<number> {
let mutex;
switch (chain) {
case 'Ethereum':
Expand All @@ -42,10 +38,7 @@ export async function getNextEvmNonce(
const txCount = await web3.eth.getTransactionCount(address);
nextEvmNonce[chain] = txCount;
}
// The SDK returns null if no transaction is sent
if (callback && (await callback(nextEvmNonce[chain]!)) === null) {
return nextEvmNonce[chain]!;
}

return nextEvmNonce[chain]!++;
});
}
Expand Down
4 changes: 2 additions & 2 deletions bouncer/shared/sol_vault_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export async function executeSolVaultSwap(
brokerFees.commissionBps,
extraParameters,
messageMetadata && {
message: messageMetadata.message as `0x${string}`,
message: messageMetadata.message,
gas_budget: messageMetadata.gasBudget,
ccm_additional_data: messageMetadata.ccmAdditionalData as `0x${string}`,
ccm_additional_data: messageMetadata.additionalData,
},
boostFeeBps ?? 0,
affiliateFees,
Expand Down
4 changes: 2 additions & 2 deletions bouncer/shared/swapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export async function newCcmMetadata(
ccmAdditionalDataArray?: string,
): Promise<CcmDepositMetadata> {
const message = ccmMessage ?? newCcmMessage(destAsset);
const ccmAdditionalData = ccmAdditionalDataArray ?? newCcmAdditionalData(destAsset, message);
const additionalData = ccmAdditionalDataArray ?? newCcmAdditionalData(destAsset, message);
const destChain = chainFromAsset(destAsset);

let userLogicGasBudget;
Expand All @@ -159,7 +159,7 @@ export async function newCcmMetadata(
return {
message,
gasBudget: userLogicGasBudget?.toString(),
ccmAdditionalData,
additionalData,
};
}

Expand Down
2 changes: 1 addition & 1 deletion bouncer/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ export async function observeSolanaCcmEvent(
// The message is being used as the main discriminator
if (matchEventName && matchSourceChain && matchMessage) {
const { additional_accounts: expectedAdditionalAccounts } =
solVersionedCcmAdditionalDataCodec.dec(messageMetadata.ccmAdditionalData!).value;
solVersionedCcmAdditionalDataCodec.dec(messageMetadata.additionalData!).value;

if (
expectedAdditionalAccounts.length !== event.data.remaining_is_writable.length ||
Expand Down
114 changes: 82 additions & 32 deletions bouncer/tests/request_swap_deposit_address_with_affiliates.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
import * as ss58 from '@chainflip/utils/ss58';
import * as base58 from '@chainflip/utils/base58';
import { isHex } from '@chainflip/utils/string';
import { hexToBytes } from '@chainflip/utils/bytes';
import { bytesToHex, hexToBytes } from '@chainflip/utils/bytes';
import { ApiPromise, Keyring } from '@polkadot/api';
import { broker, chainConstants, getInternalAsset } from '@chainflip/cli';
import { Asset, broker, chainConstants, getInternalAsset } from '@chainflip/cli';
import assert from 'assert';
import { z } from 'zod';
import { getChainflipApi } from '../shared/utils/substrate';
import { deferredPromise, handleSubstrateError, shortChainFromAsset } from '../shared/utils';
import { Chain, deferredPromise, handleSubstrateError, shortChainFromAsset } from '../shared/utils';
import { TestContext } from '../shared/utils/test_context';

function toEncodedAddress(chain: Chain, address: string) {
switch (chain) {
case 'Arbitrum':
assert(isHex(address), 'Expected hex-encoded EVM address');
return { Arb: hexToBytes(address) };
case 'Ethereum':
assert(isHex(address), 'Expected hex-encoded EVM address');
return { Eth: hexToBytes(address) };
case 'Polkadot':
return { Dot: isHex(address) ? hexToBytes(address) : ss58.decode(address).data };
case 'Solana':
return { Sol: isHex(address) ? hexToBytes(address) : base58.decode(address) };
case 'Bitcoin':
return { Btc: bytesToHex(new TextEncoder().encode(address)) };
default:
throw new Error(`Unsupported chain: ${chain}`);
}
}

export async function depositChannelCreation(testContext: TestContext) {
const keyring = new Keyring({ type: 'sr25519' });
keyring.setSS58Format(2112);

const account = keyring.addFromUri('//BROKER_2');
const account1 = keyring.addFromUri('//BROKER_2');
const account2 = keyring.addFromUri('//BROKER_1');
type NewSwapRequest = Parameters<(typeof broker)['buildExtrinsicPayload']>[0];
type SwapDetails = Parameters<(typeof broker)['requestSwapDepositAddress']>[0] & {
srcAsset: { asset: Asset; chain: Chain };
destAsset: { asset: Asset; chain: Chain };
};

const numberSchema = z.string().transform((n) => Number(n.replace(/,/g, '')));
const bigintSchema = z.string().transform((n) => BigInt(n.replace(/,/g, '')));
Expand Down Expand Up @@ -62,7 +84,7 @@ export async function depositChannelCreation(testContext: TestContext) {

const requestSwapDepositAddress = async (
chainflip: ApiPromise,
params: NewSwapRequest,
params: SwapDetails,
getNonce: () => number,
) => {
const deferred = deferredPromise<z.output<typeof eventSchema>>();
Expand All @@ -71,8 +93,35 @@ export async function depositChannelCreation(testContext: TestContext) {
const eventMatcher = chainflip.events.swapping.SwapDepositAddressReady;

const unsubscribe = await chainflip.tx.swapping
.requestSwapDepositAddressWithAffiliates(...broker.buildExtrinsicPayload(params))
.signAndSend(account, { nonce: getNonce() }, (result) => {
.requestSwapDepositAddressWithAffiliates(
getInternalAsset(params.srcAsset),
getInternalAsset(params.destAsset),
toEncodedAddress(params.destAsset.chain, params.destAddress),
params.commissionBps ?? 0,
params.ccmParams && {
message: params.ccmParams.message,
gas_budget: `0x${BigInt(params.ccmParams.gasBudget).toString(16)}`,
ccm_additional_data: params.ccmParams.additionalData,
},
getInternalAsset(params.srcAsset) === 'Btc' ? params.maxBoostFeeBps ?? 0 : 0,
(params.affiliates ?? []).map(({ account, commissionBps }) => ({
account: isHex(account) ? account : bytesToHex(ss58.decode(account).data),
bps: commissionBps,
})),
params.fillOrKillParams && {
retry_duration: params.fillOrKillParams.retryDurationBlocks,
refund_address: toEncodedAddress(
params.srcAsset.chain,
params.fillOrKillParams.refundAddress,
),
min_price: `0x${BigInt(params.fillOrKillParams.minPriceX128).toString(16)}`,
},
params.dcaParams && {
number_of_chunks: params.dcaParams.numberOfChunks,
chunk_interval: params.dcaParams.chunkIntervalBlocks,
},
)
.signAndSend(account1, { nonce: getNonce() }, (result) => {
if (!result.isInBlock) return;

if (result.dispatchError) {
Expand All @@ -91,13 +140,18 @@ export async function depositChannelCreation(testContext: TestContext) {

const event = await promise.finally(unsubscribe);

const sourceAsset = getInternalAsset({ chain: params.srcChain, asset: params.srcAsset });
const destinationAsset = getInternalAsset({ chain: params.destChain, asset: params.destAsset });

assert.strictEqual(event.sourceAsset, sourceAsset, 'source asset is wrong');
assert.strictEqual(event.destinationAsset, destinationAsset, 'destination asset is wrong');
assert.strictEqual(
event.sourceAsset,
getInternalAsset(params.srcAsset),
'source asset is wrong',
);
assert.strictEqual(
event.destinationAsset,
getInternalAsset(params.destAsset),
'destination asset is wrong',
);

const destChain = shortChainFromAsset(destinationAsset);
const destChain = shortChainFromAsset(getInternalAsset(params.destAsset));
const transformDestAddress = addressTransforms[destChain];

assert.strictEqual(
Expand All @@ -117,7 +171,7 @@ export async function depositChannelCreation(testContext: TestContext) {
event.refundParameters?.retryDuration,
params.fillOrKillParams.retryDurationBlocks,
);
const sourceChain = shortChainFromAsset(sourceAsset);
const sourceChain = shortChainFromAsset(getInternalAsset(params.srcAsset));
const transformRefundAddress = addressTransforms[sourceChain];
assert.strictEqual(
transformRefundAddress(event.refundParameters!.refundAddress[sourceChain]!),
Expand Down Expand Up @@ -147,7 +201,7 @@ export async function depositChannelCreation(testContext: TestContext) {
assert.strictEqual(event.channelMetadata.gasBudget, BigInt(params.ccmParams.gasBudget));
assert.strictEqual(
event.channelMetadata.ccmAdditionalData,
params.ccmParams.ccmAdditionalData === '0x' ? '' : params.ccmParams.ccmAdditionalData,
params.ccmParams.additionalData === '0x' ? '' : params.ccmParams.additionalData,
);
}
};
Expand All @@ -172,62 +226,58 @@ export async function depositChannelCreation(testContext: TestContext) {
addrs.map(
(destAddress) =>
({
srcAsset: 'FLIP',
srcChain: 'Ethereum',
destChain,
destAsset: chainConstants[destChain].assets[0],
srcAsset: { asset: 'FLIP', chain: 'Ethereum' },
destAsset: { asset: chainConstants[destChain].assets[0], chain: destChain },
destAddress,
}) as NewSwapRequest,
}) as SwapDetails,
),
);

const refundCases = entries(addresses).flatMap(([srcChain, addrs]) =>
addrs.map(
(refundAddress) =>
({
destAsset: 'FLIP',
destChain: 'Ethereum',
destAsset: { asset: 'FLIP', chain: 'Ethereum' },
destAddress: addresses.Ethereum[0],
srcChain,
srcAsset: chainConstants[srcChain].assets[0],
srcAsset: { asset: chainConstants[srcChain].assets[0], chain: srcChain },
fillOrKillParams: {
refundAddress,
minPriceX128: '1',
retryDurationBlocks: 100,
},
}) as NewSwapRequest,
}) as SwapDetails,
),
);

const withDca: NewSwapRequest = {
const withDca: SwapDetails = {
...refundCases[0],
dcaParams: {
numberOfChunks: 7200,
chunkIntervalBlocks: 2,
},
};

const withCommission: NewSwapRequest = {
const withCommission: SwapDetails = {
...baseCases[0],
commissionBps: 100,
};

const withAffiliates: NewSwapRequest = {
const withAffiliates: SwapDetails = {
...baseCases[0],
affiliates: [{ account: account2.address, commissionBps: 100 }],
};

const withCcm: NewSwapRequest = {
const withCcm: SwapDetails = {
...baseCases.find((c) => c.destChain === 'Arbitrum')!,
ccmParams: {
message: '0xcafebabe',
gasBudget: '1000000',
ccmAdditionalData: '0x',
additionalData: '0x',
},
};

await using api = await getChainflipApi();
let nonce = (await api.rpc.system.accountNextIndex(account.address)).toJSON() as number;
let nonce = (await api.rpc.system.accountNextIndex(account1.address)).toJSON() as number;

const allCases = [...baseCases, ...refundCases, withDca, withCommission, withAffiliates, withCcm];
const results = await Promise.allSettled(
Expand Down

0 comments on commit c70d2c1

Please sign in to comment.