Skip to content

Commit

Permalink
chore: add vault swap specific ccm metadata lengths
Browse files Browse the repository at this point in the history
  • Loading branch information
albert-llimos committed Dec 16, 2024
1 parent 988e485 commit 8fb5d7c
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 77 deletions.
2 changes: 1 addition & 1 deletion bouncer/shared/evm_vault_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export async function executeEvmVaultSwap(
} as const;
const txOptions = {
// This is run with fresh addresses to prevent nonce issues. Will be 1 for ERC20s.
gasLimit: srcChain === Chains.Arbitrum ? 10000000n : 200000n,
gasLimit: srcChain === Chains.Arbitrum ? 50000000n : 1000000n,
} as const;

const receipt = await executeSwap(
Expand Down
144 changes: 77 additions & 67 deletions bouncer/shared/swapping.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { InternalAsset as Asset } from '@chainflip/cli';
import { Keypair, PublicKey } from '@solana/web3.js';
import Web3 from 'web3';
import { u8aToHex } from '@polkadot/util';
import { randomAsHex, randomAsNumber } from '../polkadot/util-crypto';
import { randomAsHex } from '../polkadot/util-crypto';
import { performSwap, performVaultSwap } from '../shared/perform_swap';
import {
newAddress,
Expand All @@ -18,56 +17,8 @@ import { BtcAddressType } from '../shared/new_btc_address';
import { CcmDepositMetadata } from '../shared/new_swap';
import { SwapContext, SwapStatus } from './swap_context';

enum SolidityType {
Uint256 = 'uint256',
String = 'string',
Bytes = 'bytes',
Address = 'address',
}

let swapCount = 1;

function newAbiEncodedMessage(types?: SolidityType[]): string {
const web3 = new Web3();

let typesArray: SolidityType[] = [];
if (types === undefined) {
const numElements = Math.floor(Math.random() * (Object.keys(SolidityType).length / 2)) + 1;
for (let i = 0; i < numElements; i++) {
typesArray.push(
Object.values(SolidityType)[
Math.floor(Math.random() * (Object.keys(SolidityType).length / 2))
],
);
}
} else {
typesArray = types;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const variables: any[] = [];

for (let i = 0; i < typesArray.length; i++) {
switch (typesArray[i]) {
case SolidityType.Uint256:
variables.push(randomAsNumber());
break;
case SolidityType.String:
variables.push(Math.random().toString(36).substring(2));
break;
case SolidityType.Bytes:
variables.push(randomAsHex(Math.floor(Math.random() * 100) + 1));
break;
case SolidityType.Address:
variables.push(randomAsHex(20));
break;
// Add more cases for other Solidity types as needed
default:
throw new Error(`Unsupported Solidity type: ${typesArray[i]}`);
}
}
return web3.eth.abi.encodeParameters(typesArray, variables);
}

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

Expand Down Expand Up @@ -95,30 +46,45 @@ export function newSolanaCcmAdditionalData(maxAccounts: number) {
return u8aToHex(solCcmAdditionalDataCodec.enc(cfParameters));
}

// Solana CCM-related parameters. These are values in the protocol.
const maxCcmBytesSol = 705;
const maxCcmBytesUsdc = 492;
const bytesPerAccount = 33;

// Generate random bytes. Setting a minimum length of 10 because very short messages can end up
// with the SC returning an ASCII character in SwapDepositAddressReady.
function newCcmArbitraryBytes(maxLength: number): string {
return randomAsHex(Math.floor(Math.random() * Math.max(0, maxLength - 10)) + 10);
}

function newCcmAdditionalData(destAsset: Asset, message?: string): string {
// Protocol limits
const MAX_CCM_MSG_LENGTH = 9_000;
const MAX_CCM_ADDITIONAL_DATA_LENGTH = 1000;
// Solana transactions have a length of 1232. Cappig it to some reasonable values
// that when construction the call the Solana length is not exceeded.
// TODO: Revisit these values once cfParameters is encoded in the SDK for SolVaultSwaps.
const MAX_SOL_VAULT_SWAP_CCM_MESSAGE_LENGTH = 500;
const MAX_SOL_VAULT_SWAP_ADDITIONAL_METADATA_LENGTH = 1000;

// Solana CCM-related parameters. These are limits in the protocol.
const MAX_CCM_BYTES_SOL = 705;
const MAX_CCM_BYTES_USDC = 492;
const bytesPerAccount = 33;

function newCcmAdditionalData(destAsset: Asset, message?: string, maxLength?: number): string {
const destChain = chainFromAsset(destAsset);
let length: number;

switch (destChain) {
case 'Ethereum':
case 'Arbitrum':
// Cf Parameters should be ignored by the protocol for any chain other than Solana
return newCcmArbitraryBytes(100);
length = MAX_CCM_ADDITIONAL_DATA_LENGTH;
if (maxLength !== undefined) {
length = Math.min(length, maxLength);
}
return newCcmArbitraryBytes(length);
case 'Solana': {
const messageLength = (message!.length - 2) / 2;
const maxAccounts = Math.floor(
((destAsset === 'Sol' ? maxCcmBytesSol : maxCcmBytesUsdc) - messageLength) /
bytesPerAccount,
);
length = (destAsset === 'Sol' ? MAX_CCM_BYTES_SOL : MAX_CCM_BYTES_USDC) - messageLength;
if (maxLength !== undefined) {
length = Math.min(length, maxLength);
}
const maxAccounts = Math.floor(length / bytesPerAccount);

// The maximum number of extra accounts that can be passed is limited by the tx size
// and therefore also depends on the message length.
Expand All @@ -129,28 +95,38 @@ function newCcmAdditionalData(destAsset: Asset, message?: string): string {
}
}

function newCcmMessage(destAsset: Asset): string {
function newCcmMessage(destAsset: Asset, maxLength?: number): string {
const destChain = chainFromAsset(destAsset);
let length: number;

switch (destChain) {
case 'Ethereum':
case 'Arbitrum':
return newAbiEncodedMessage();
length = MAX_CCM_MSG_LENGTH;
break;
case 'Solana':
return newCcmArbitraryBytes(destAsset === 'Sol' ? maxCcmBytesSol : maxCcmBytesUsdc);
length = destAsset === 'Sol' ? MAX_CCM_BYTES_SOL : MAX_CCM_BYTES_USDC;
break;
default:
throw new Error(`Unsupported chain: ${destChain}`);
}

if (maxLength !== undefined) {
length = Math.min(length, maxLength);
}

return newCcmArbitraryBytes(length);
}

export function newCcmMetadata(
sourceAsset: Asset,
destAsset: Asset,
ccmMessage?: string,
gasBudgetFraction?: number,
cfParamsArray?: string,
ccmAdditionalDataArray?: string,
): CcmDepositMetadata {
const message = ccmMessage ?? newCcmMessage(destAsset);
const ccmAdditionalData = cfParamsArray ?? newCcmAdditionalData(destAsset, message);
const ccmAdditionalData = ccmAdditionalDataArray ?? newCcmAdditionalData(destAsset, message);
const gasDiv = gasBudgetFraction ?? 2;

const gasBudget = Math.floor(
Expand All @@ -165,6 +141,40 @@ export function newCcmMetadata(
};
}

// Vault swaps have some limitations depending on the source chain
export function newVaultSwapCcmMetadata(
sourceAsset: Asset,
destAsset: Asset,
ccmMessage?: string,
gasBudgetFraction?: number,
ccmAdditionalDataArray?: string,
): CcmDepositMetadata {
const sourceChain = chainFromAsset(sourceAsset);
let messageMaxLength;
let metadataMaxLength;

// Solana has restrictions on transaction length
if (sourceChain === 'Solana') {
messageMaxLength = MAX_SOL_VAULT_SWAP_CCM_MESSAGE_LENGTH;
metadataMaxLength = MAX_SOL_VAULT_SWAP_ADDITIONAL_METADATA_LENGTH;
if (ccmMessage && ccmMessage.length / 2 > messageMaxLength) {
throw new Error(
`Message length for Solana vault swap must be less than ${messageMaxLength} bytes`,
);
}
if (ccmAdditionalDataArray && ccmAdditionalDataArray.length / 2 > metadataMaxLength) {
throw new Error(
`Additional data length for Solana vault swap must be less than ${metadataMaxLength} bytes`,
);
}
}

const message = ccmMessage ?? newCcmMessage(destAsset, messageMaxLength);
const ccmAdditionalData =
ccmAdditionalDataArray ?? newCcmAdditionalData(destAsset, message, metadataMaxLength);
return newCcmMetadata(sourceAsset, destAsset, message, gasBudgetFraction, ccmAdditionalData);
}

export async function prepareSwap(
sourceAsset: Asset,
destAsset: Asset,
Expand Down
25 changes: 16 additions & 9 deletions bouncer/tests/all_swaps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { InternalAsset as Asset, InternalAssets as Assets } from '@chainflip/cli';
import { ExecutableTest } from '../shared/executable_test';
import { SwapParams } from '../shared/perform_swap';
import { newCcmMetadata, testSwap, testVaultSwap } from '../shared/swapping';
import {
newCcmMetadata,
newVaultSwapCcmMetadata,
testSwap,
testVaultSwap,
} from '../shared/swapping';
import { btcAddressTypes } from '../shared/new_btc_address';
import { ccmSupportedChains, chainFromAsset, VaultSwapParams } from '../shared/utils';

Expand All @@ -17,26 +22,28 @@ async function main() {
functionCall: typeof testSwap | typeof testVaultSwap,
ccmSwap: boolean = false,
) {
let ccmSwapMetadata;
if (ccmSwap) {
ccmSwapMetadata =
functionCall === testSwap
? newCcmMetadata(sourceAsset, destAsset)
: newVaultSwapCcmMetadata(sourceAsset, destAsset);
}

if (destAsset === 'Btc') {
const btcAddressTypesArray = Object.values(btcAddressTypes);
allSwaps.push(
functionCall(
sourceAsset,
destAsset,
btcAddressTypesArray[Math.floor(Math.random() * btcAddressTypesArray.length)],
ccmSwap ? newCcmMetadata(sourceAsset, destAsset) : undefined,
ccmSwapMetadata,
testAllSwaps.swapContext,
),
);
} else {
allSwaps.push(
functionCall(
sourceAsset,
destAsset,
undefined,
ccmSwap ? newCcmMetadata(sourceAsset, destAsset) : undefined,
testAllSwaps.swapContext,
),
functionCall(sourceAsset, destAsset, undefined, ccmSwapMetadata, testAllSwaps.swapContext),
);
}
}
Expand Down

0 comments on commit 8fb5d7c

Please sign in to comment.