Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Extend Evm Vault swaps functionality #5344

Merged
merged 43 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
6f26b19
feat: witnessing btc smart contract swaps
msgmaxim Oct 15, 2024
a7fd6d4
Merge branch 'main' into feat/btc-smart-contract-witnessing
msgmaxim Oct 16, 2024
d0446ae
chore: address minor review comments
msgmaxim Oct 16, 2024
9c2e4c3
chore: start implementation
albert-llimos Oct 16, 2024
6557606
test: fix deposit witnessing tests
msgmaxim Oct 17, 2024
e682f16
chore: add intial scale encoding for cfParameters
albert-llimos Oct 17, 2024
115342e
chore: improve logic
albert-llimos Oct 17, 2024
4256a5b
fix: address RuntimeCall size limit
msgmaxim Oct 18, 2024
7f91a99
Merge branch 'main' into feat/btc-smart-contract-witnessing
msgmaxim Oct 18, 2024
4234046
chore: address clippy
msgmaxim Oct 18, 2024
bd67b36
chore: more cleanup and refactoring
albert-llimos Oct 18, 2024
b510b81
chore: add contract swaps to dca test
albert-llimos Oct 18, 2024
11d4fa9
chore: merge from base
albert-llimos Oct 18, 2024
f436393
chore: engine refactor
albert-llimos Oct 18, 2024
e27808c
chore: refactor createEvmWallet
albert-llimos Oct 21, 2024
3f90224
chore: merge from main
albert-llimos Oct 21, 2024
d14c40d
chore: cleanup
albert-llimos Oct 21, 2024
7a80f89
chore: add MAX_VAULT_SWAP_ATTRIBUTES_LENGTH
albert-llimos Oct 21, 2024
cd8fa68
chore: refactor bouncer
albert-llimos Oct 21, 2024
da74dbb
chore: refactor into common for reusal
albert-llimos Oct 21, 2024
1851094
chore: rename attributes to parameters
albert-llimos Oct 21, 2024
7305042
chore: pass extra parameters to sdk
albert-llimos Oct 21, 2024
f9e67ab
chore: fix issues
albert-llimos Oct 22, 2024
de1bf99
chore: engine renaming
albert-llimos Oct 22, 2024
5eba5c7
chore: update SDK with new encoding logic
albert-llimos Oct 23, 2024
b14cb63
chore: remove unnecessary bouncer ts-scale
albert-llimos Oct 23, 2024
c06e294
chore: lint
albert-llimos Oct 23, 2024
854cbaf
chore: fix merge conflicts
albert-llimos Oct 23, 2024
c621a7d
chore: update to right name
albert-llimos Oct 23, 2024
2381415
chore: fix missing rename
albert-llimos Oct 23, 2024
cfe63fd
chore: add beneficiares and make FoK mandatory
albert-llimos Oct 23, 2024
399846f
chore: lint
albert-llimos Oct 23, 2024
d77a0da
chore: update broker_fees and cli
albert-llimos Oct 23, 2024
ea61dce
chore: update with hardcoded cfParameters
albert-llimos Oct 24, 2024
2282405
chore: bump sdk
albert-llimos Oct 24, 2024
de719c5
fix: simplify cf params decoding
dandanlen Oct 24, 2024
e3b8314
chore: downgrade error -> warning
dandanlen Oct 24, 2024
1d9b88e
chore: merge form main and fix conflicts
albert-llimos Oct 25, 2024
785ab48
chore: bump sdk with new broker_fees type
albert-llimos Oct 25, 2024
2033df7
chore: fix failing test
albert-llimos Oct 25, 2024
99310e5
chore: bump sdk
albert-llimos Oct 25, 2024
2d070ea
chore: lint
albert-llimos Oct 25, 2024
3bd922a
Merge commit 'd4eff081774475a80b140dc554bb3ae6ebf121b3' into feat/evm…
albert-llimos Oct 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bouncer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"prettier:write": "prettier --write ."
},
"dependencies": {
"@chainflip/cli": "1.6.3",
"@chainflip/cli": "1.8.0-cf-parameters-rename.5",
"@chainflip/utils": "^0.4.0",
"@coral-xyz/anchor": "^0.30.1",
"@iarna/toml": "^2.2.5",
Expand Down
18 changes: 9 additions & 9 deletions bouncer/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

110 changes: 62 additions & 48 deletions bouncer/shared/contract_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,67 @@ import {
approveVault,
Asset as SCAsset,
Chains,
InternalAsset,
Chain,
} from '@chainflip/cli';
import { HDNodeWallet, Wallet, getDefaultProvider } from 'ethers';
import { HDNodeWallet } from 'ethers';
import { randomBytes } from 'crypto';
import {
observeBalanceIncrease,
getContractAddress,
observeCcmReceived,
amountToFineAmount,
defaultAssetAmounts,
chainFromAsset,
getEvmEndpoint,
assetDecimals,
stateChainAssetFromAsset,
chainGasAsset,
createEvmWalletAndFund,
newAddress,
} from './utils';
import { getBalance } from './get_balance';
import { CcmDepositMetadata } from '../shared/new_swap';
import { send } from './send';
import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from '../shared/new_swap';
import { SwapContext, SwapStatus } from './swap_context';

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

export async function executeContractSwap(
srcAsset: Asset,
sourceAsset: Asset,
destAsset: Asset,
destAddress: string,
wallet: HDNodeWallet,
messageMetadata?: CcmDepositMetadata,
amount?: string,
boostFeeBps?: number,
fillOrKillParams?: FillOrKillParamsX128,
dcaParams?: DcaParams,
wallet?: HDNodeWallet,
): ReturnType<typeof executeSwap> {
const srcChain = chainFromAsset(srcAsset);
const srcChain = chainFromAsset(sourceAsset);
const destChain = chainFromAsset(destAsset);
const amountToSwap = amount ?? defaultAssetAmounts(sourceAsset);

const refundAddress = await newAddress(sourceAsset, randomBytes(32).toString('hex'));
const fokParams = fillOrKillParams ?? {
retryDurationBlocks: 0,
refundAddress,
minPriceX128: '0',
};

const evmWallet = wallet ?? (await createEvmWalletAndFund(sourceAsset));

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 approveTokenVault(
sourceAsset,
(BigInt(amountToFineAmount(amountToSwap, assetDecimals(sourceAsset))) * 100n).toString(),
evmWallet,
);
}

const networkOptions = {
signer: wallet,
signer: evmWallet,
network: 'localnet',
vaultContractAddress: getContractAddress(srcChain, 'VAULT'),
srcTokenContractAddress: getContractAddress(srcChain, srcAsset),
srcTokenContractAddress: getContractAddress(srcChain, sourceAsset),
} as const;
const txOptions = {
// This is run with fresh addresses to prevent nonce issues. Will be 1 for ERC20s.
Expand All @@ -55,15 +78,21 @@ export async function executeContractSwap(
destAsset: stateChainAssetFromAsset(destAsset),
// It is important that this is large enough to result in
// an amount larger than existential (e.g. on Polkadot):
amount: amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)),
amount: amountToFineAmount(amountToSwap, assetDecimals(sourceAsset)),
destAddress,
srcAsset: stateChainAssetFromAsset(srcAsset),
srcAsset: stateChainAssetFromAsset(sourceAsset),
srcChain,
ccmParams: messageMetadata && {
gasBudget: messageMetadata.gasBudget.toString(),
message: messageMetadata.message,
cfParameters: messageMetadata.cfParameters,
ccmAdditionalData: messageMetadata.ccmAdditionalData,
},
// The SDK will encode these parameters and the ccmAdditionalData
// into the `cfParameters` field for the vault swap.
boostFeeBps,
fillOrKillParams: fokParams,
dcaParams,
beneficiaries: undefined,
} as ExecuteSwapParams,
networkOptions,
txOptions,
Expand All @@ -86,37 +115,18 @@ export async function performSwapViaContract(
messageMetadata?: CcmDepositMetadata,
swapContext?: SwapContext,
log = true,
amount?: string,
boostFeeBps?: number,
fillOrKillParams?: FillOrKillParamsX128,
dcaParams?: DcaParams,
): Promise<ContractSwapParams> {
const tag = swapTag ?? '';

const srcChain = chainFromAsset(sourceAsset);

// Generate a new wallet for each contract swap to prevent nonce issues when running in parallel
// with other swaps via deposit channels.
const mnemonic = Wallet.createRandom().mnemonic?.phrase ?? '';
if (mnemonic === '') {
throw new Error('Failed to create random mnemonic');
}
const wallet = Wallet.fromPhrase(mnemonic).connect(getDefaultProvider(getEvmEndpoint(srcChain)));
const amountToSwap = amount ?? defaultAssetAmounts(sourceAsset);

try {
// Fund new key with native asset and asset to swap.
await send(chainGasAsset(srcChain) as InternalAsset, wallet.address);
await send(sourceAsset, wallet.address);

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 approveTokenVault(
sourceAsset,
(
BigInt(amountToFineAmount(defaultAssetAmounts(sourceAsset), assetDecimals(sourceAsset))) *
100n
).toString(),
wallet,
);
}
swapContext?.updateStatus(swapTag, SwapStatus.ContractApproved);
// Generate a new wallet for each contract swap to prevent nonce issues when running in parallel
// with other swaps via deposit channels.
const wallet = await createEvmWalletAndFund(sourceAsset);

const oldBalance = await getBalance(destAsset, destAddress);
if (log) {
Expand All @@ -133,8 +143,12 @@ export async function performSwapViaContract(
sourceAsset,
destAsset,
destAddress,
wallet,
messageMetadata,
amountToSwap,
boostFeeBps,
fillOrKillParams,
dcaParams,
wallet,
);
swapContext?.updateStatus(swapTag, SwapStatus.ContractExecuted);

Expand Down Expand Up @@ -171,24 +185,24 @@ export async function performSwapViaContract(
throw new Error(`${tag} ${err}`);
}
}
export async function approveTokenVault(srcAsset: Asset, amount: string, wallet: HDNodeWallet) {
if (!erc20Assets.includes(srcAsset)) {
throw new Error(`Unsupported asset, not an ERC20: ${srcAsset}`);
export async function approveTokenVault(sourceAsset: Asset, amount: string, wallet: HDNodeWallet) {
if (!erc20Assets.includes(sourceAsset)) {
throw new Error(`Unsupported asset, not an ERC20: ${sourceAsset}`);
}

const chain = chainFromAsset(srcAsset as Asset);
const chain = chainFromAsset(sourceAsset as Asset);

await approveVault(
{
amount,
srcChain: chain as Chain,
srcAsset: stateChainAssetFromAsset(srcAsset) as SCAsset,
srcAsset: stateChainAssetFromAsset(sourceAsset) as SCAsset,
},
{
signer: wallet,
network: 'localnet',
vaultContractAddress: getContractAddress(chain, 'VAULT'),
srcTokenContractAddress: getContractAddress(chain, srcAsset),
srcTokenContractAddress: getContractAddress(chain, sourceAsset),
},
// This is run with fresh addresses to prevent nonce issues
{
Expand Down
2 changes: 1 addition & 1 deletion bouncer/shared/new_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function newSwap(
ccmParams: messageMetadata && {
message: messageMetadata.message as `0x${string}`,
gasBudget: messageMetadata.gasBudget.toString(),
cfParameters: messageMetadata.cfParameters as `0x${string}`,
ccmAdditionalData: messageMetadata.ccmAdditionalData as `0x${string}`,
},
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 @@ -70,7 +70,7 @@ export async function requestNewSwap(
? event.data.channelMetadata !== null &&
event.data.channelMetadata.message === messageMetadata.message &&
event.data.channelMetadata.gasBudget.replace(/,/g, '') === messageMetadata.gasBudget &&
event.data.channelMetadata.cfParameters === messageMetadata.cfParameters
event.data.channelMetadata.ccmAdditionalData === messageMetadata.ccmAdditionalData
: event.data.channelMetadata === null;

return destAddressMatches && destAssetMatches && sourceAssetMatches && ccmMetadataMatches;
Expand Down
4 changes: 1 addition & 3 deletions bouncer/shared/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import { sendSolUsdc } from './send_solusdc';
const cfTesterAbi = await getCFTesterAbi();

export async function send(asset: Asset, address: string, amount?: string, log = true) {
// TODO: Remove this any when we have Sol assets in the Asset type.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
switch (asset as any) {
switch (asset) {
case 'Btc':
await sendBtc(address, amount ?? defaultAssetAmounts(asset));
break;
Expand Down
11 changes: 1 addition & 10 deletions bouncer/shared/swap_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import assert from 'assert';
export enum SwapStatus {
Initiated,
Funded,
// Contract swap specific statuses
ContractApproved,
ContractExecuted,
SwapScheduled,
Success,
Expand Down Expand Up @@ -37,16 +35,9 @@ export class SwapContext {
);
break;
}
case SwapStatus.ContractApproved: {
assert(
currentStatus === SwapStatus.Initiated,
`Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`,
);
break;
}
case SwapStatus.ContractExecuted: {
assert(
currentStatus === SwapStatus.ContractApproved,
currentStatus === SwapStatus.Initiated,
`Unexpected status transition for ${tag}. Transitioning from ${currentStatus} to ${status}`,
);
break;
Expand Down
16 changes: 8 additions & 8 deletions bouncer/shared/swapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
defaultAssetAmounts,
ccmSupportedChains,
assetDecimals,
solCfParamsCodec,
solCcmAdditionalDataCodec,
} from '../shared/utils';
import { BtcAddressType } from '../shared/new_btc_address';
import { CcmDepositMetadata } from '../shared/new_swap';
Expand Down Expand Up @@ -69,7 +69,7 @@ function newAbiEncodedMessage(types?: SolidityType[]): string {
return web3.eth.abi.encodeParameters(typesArray, variables);
}

export function newSolanaCfParameters(maxAccounts: number) {
export function newSolanaCcmAdditionalData(maxAccounts: number) {
const cfReceiverAddress = getContractAddress('Solana', 'CFTESTER');

const fallbackAddress = Keypair.generate().publicKey.toBytes();
Expand All @@ -93,7 +93,7 @@ export function newSolanaCfParameters(maxAccounts: number) {
fallback_address: fallbackAddress,
};

return u8aToHex(solCfParamsCodec.enc(cfParameters));
return u8aToHex(solCcmAdditionalDataCodec.enc(cfParameters));
}

// Solana CCM-related parameters. These are values in the protocol.
Expand All @@ -107,7 +107,7 @@ function newCcmArbitraryBytes(maxLength: number): string {
return randomAsHex(Math.floor(Math.random() * Math.max(0, maxLength - 10)) + 10);
}

function newCfParameters(destAsset: Asset, message?: string): string {
function newCcmAdditionalData(destAsset: Asset, message?: string): string {
const destChain = chainFromAsset(destAsset);
switch (destChain) {
case 'Ethereum':
Expand All @@ -123,7 +123,7 @@ function newCfParameters(destAsset: Asset, message?: string): string {

// The maximum number of extra accounts that can be passed is limited by the tx size
// and therefore also depends on the message length.
return newSolanaCfParameters(maxAccounts);
return newSolanaCcmAdditionalData(maxAccounts);
}
default:
throw new Error(`Unsupported chain: ${destChain}`);
Expand Down Expand Up @@ -151,7 +151,7 @@ export function newCcmMetadata(
cfParamsArray?: string,
): CcmDepositMetadata {
const message = ccmMessage ?? newCcmMessage(destAsset);
const cfParameters = cfParamsArray ?? newCfParameters(destAsset, message);
const ccmAdditionalData = cfParamsArray ?? newCcmAdditionalData(destAsset, message);
const gasDiv = gasBudgetFraction ?? 2;

const gasBudget = Math.floor(
Expand All @@ -162,7 +162,7 @@ export function newCcmMetadata(
return {
message,
gasBudget,
cfParameters,
ccmAdditionalData,
};
}

Expand Down Expand Up @@ -250,7 +250,7 @@ export async function testSwapViaContract(
destAsset,
addressType,
messageMetadata,
(tagSuffix ?? '') + ' Contract',
(tagSuffix ?? '') + 'Contract',
log,
swapContext,
);
Expand Down
Loading
Loading