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

chore: replace manual scale encoding for ts-scale #5335

Merged
merged 6 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions bouncer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"md5": "^2.3.0",
"minimist": "^1.2.8",
"rxjs": "^7.8.1",
"scale-ts": "1.6.0",
"tiny-secp256k1": "^2.2.1",
"toml": "^3.0.0",
"web3": "^1.9.0",
Expand Down
6 changes: 4 additions & 2 deletions bouncer/pnpm-lock.yaml

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

52 changes: 18 additions & 34 deletions bouncer/shared/swapping.ts
Original file line number Diff line number Diff line change
@@ -1,6 +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 { performSwap } from '../shared/perform_swap';
import {
Expand All @@ -11,6 +12,7 @@ import {
defaultAssetAmounts,
ccmSupportedChains,
assetDecimals,
solCfParamsCodec,
} from '../shared/utils';
import { BtcAddressType } from '../shared/new_btc_address';
import { CcmDepositMetadata } from '../shared/new_swap';
Expand Down Expand Up @@ -67,49 +69,31 @@ function newAbiEncodedMessage(types?: SolidityType[]): string {
return web3.eth.abi.encodeParameters(typesArray, variables);
}

function newSolanaCfParameters(maxAccounts: number) {
function arrayToHexString(byteArray: Uint8Array): string {
return (
'0x' +
Array.from(byteArray)
// eslint-disable-next-line no-bitwise
.map((byte) => ('0' + (byte & 0xff).toString(16)).slice(-2))
.join('')
);
}

const cfReceiver = {
pubkey: getContractAddress('Solana', 'CFTESTER'),
is_writable: false,
};
export function newSolanaCfParameters(maxAccounts: number) {
const cfReceiverAddress = getContractAddress('Solana', 'CFTESTER');

// Convert the public keys and is_writable fields to byte arrays
const cfReceiverBytes = new Uint8Array([
...new PublicKey(cfReceiver.pubkey).toBytes(),
cfReceiver.is_writable ? 1 : 0,
]);

const fallbackAddrBytes = new PublicKey(getContractAddress('Solana', 'FALLBACK')).toBytes();
const fallbackAddress = Keypair.generate().publicKey.toBytes();

const remainingAccounts = [];
const numRemainingAccounts = Math.floor(Math.random() * maxAccounts);

for (let i = 0; i < numRemainingAccounts; i++) {
remainingAccounts.push(
new Uint8Array([...Keypair.generate().publicKey.toBytes(), Math.random() < 0.5 ? 1 : 0]),
);
remainingAccounts.push({
pubkey: Keypair.generate().publicKey.toBytes(),
is_writable: Math.random() < 0.5,
});
}

// Concatenate the byte arrays
const cfParameters = new Uint8Array([
...cfReceiverBytes,
// Inserted by the codec::Encode
4 * remainingAccounts.length,
...remainingAccounts.flatMap((account) => Array.from(account)),
...fallbackAddrBytes,
]);
const cfParameters = {
cf_receiver: {
pubkey: new PublicKey(cfReceiverAddress).toBytes(),
is_writable: false,
},
remaining_accounts: remainingAccounts,
fallback_address: fallbackAddress,
};

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

// Solana CCM-related parameters. These are values in the protocol.
Expand Down
92 changes: 40 additions & 52 deletions bouncer/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import Web3 from 'web3';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { hexToU8a, u8aToHex, BN } from '@polkadot/util';
import { Vector, bool, Struct, Bytes as TsBytes } from 'scale-ts';
import BigNumber from 'bignumber.js';
import { EventParser, BorshCoder } from '@coral-xyz/anchor';
import { ISubmittableResult } from '@polkadot/types/types';
Expand Down Expand Up @@ -51,6 +52,20 @@ const isSDKChain = (chain: Chain): chain is SDKChain => chain in chainConstants;

export const solanaNumberOfNonces = 10;

export const solCfParamsCodec = Struct({
cf_receiver: Struct({
pubkey: TsBytes(32),
is_writable: bool,
}),
remaining_accounts: Vector(
Struct({
pubkey: TsBytes(32),
is_writable: bool,
}),
),
fallback_address: TsBytes(32),
});

export function getContractAddress(chain: Chain, contract: string): string {
switch (chain) {
case 'Ethereum':
Expand Down Expand Up @@ -105,9 +120,6 @@ export function getContractAddress(chain: Chain, contract: string): string {
return '8pBPaVfTAcjLeNfC187Fkvi9b1XEFhRNJ95BQXXVksmH';
case 'SWAP_ENDPOINT':
return '35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT';
case 'FALLBACK':
/// 3wDSVR6YSRDFiWdwsnZZRjAKHKvsmb4fouYVqoBt5yd4vSrY7aQdvtLJKMvEb3AMWGD5fxunfdotvwPwnSChWMWx
return 'CFf51BPWnybvgbZrxy61s4SCCvEohBC7achsPLuoACUG';
default:
throw new Error(`Unsupported contract: ${contract}`);
}
Expand Down Expand Up @@ -713,39 +725,6 @@ export async function observeSolanaCcmEvent(
sourceAddress: string | null,
messageMetadata: CcmDepositMetadata,
): Promise<ContractEvent> {
function decodeExpectedCfParameters(cfParametersHex: string) {
// Convert the hexadecimal string back to a byte array
const cfParameters = new Uint8Array(
cfParametersHex
.slice(2)
.match(/.{1,2}/g)
?.map((byte) => parseInt(byte, 16)) ?? [],
);

const publicKeySize = 32;
const remainingAccountSize = publicKeySize + 1;

// Extra byte for the encoded length
const remainingAccountsBytes = cfParameters.slice(remainingAccountSize + 1, -publicKeySize);

const remainingAccounts = [];
const remainingIsWritable = [];

// Extract the remaining bytes in groups of publicKeySize + 1
for (let i = 0; i < remainingAccountsBytes.length; i += remainingAccountSize) {
const publicKeyBytes = remainingAccountsBytes.slice(i, i + publicKeySize);
const isWritable = remainingAccountsBytes[i + publicKeySize];

remainingAccounts.push(new PublicKey(publicKeyBytes));
remainingIsWritable.push(Boolean(isWritable));
}

// fallback account
const fallbackAccount = cfParameters.slice(-publicKeySize);

return { remainingAccounts, remainingIsWritable, fallbackAccount };
}

const connection = getSolConnection();
const idl = cfTesterIdl;
const cfTesterAddress = new PublicKey(getContractAddress('Solana', 'CFTESTER'));
Expand All @@ -767,31 +746,40 @@ export async function observeSolanaCcmEvent(

// The message is being used as the main discriminator
if (matchEventName && matchSourceChain && matchMessage) {
const {
remainingAccounts: expectedRemainingAccounts,
remainingIsWritable: expectedRemainingIsWritable,
} = decodeExpectedCfParameters(messageMetadata.cfParameters!);
const { remaining_accounts: expectedRemainingAccounts } = solCfParamsCodec.dec(
messageMetadata.cfParameters!,
);

if (
expectedRemainingIsWritable.length !== event.data.remaining_is_writable.length ||
expectedRemainingIsWritable.toString() !== event.data.remaining_is_writable.toString()
expectedRemainingAccounts.length !== event.data.remaining_is_writable.length ||
expectedRemainingAccounts.length !== event.data.remaining_pubkeys.length
) {
throw new Error(
`Unexpected remaining account is writable: ${event.data.remaining_is_writable}, expecting ${expectedRemainingIsWritable}`,
`Unexpected remaining accounts length: ${expectedRemainingAccounts.length}, expecting ${event.data.remaining_is_writable.length}, ${event.data.remaining_pubkeys.length}`,
);
}

if (event.data.remaining_is_signer.some((value: boolean) => value === true)) {
throw new Error(`Expected all remaining accounts to be read-only`);
for (let index = 0; index < expectedRemainingAccounts.length; index++) {
if (
expectedRemainingAccounts[index].is_writable.toString() !==
event.data.remaining_is_writable[index].toString()
) {
throw new Error(
`Unexpected remaining account is_writable: ${event.data.remaining_is_writable[index]}, expecting ${expectedRemainingAccounts[index].is_writable}`,
);
}
const expectedPubkey = new PublicKey(
expectedRemainingAccounts[index].pubkey,
).toString();
if (expectedPubkey !== event.data.remaining_pubkeys[index].toString()) {
throw new Error(
`Unexpected remaining account pubkey: ${event.data.remaining_pubkeys[index].toString()}, expecting ${expectedPubkey}`,
);
}
}

if (
expectedRemainingAccounts.length !== event.data.remaining_pubkeys.length ||
expectedRemainingAccounts.toString() !== event.data.remaining_pubkeys.toString()
) {
throw new Error(
`Unexpected remaining accounts: ${event.data.remaining_pubkeys}, expecting ${expectedRemainingAccounts}`,
);
if (event.data.remaining_is_signer.some((value: boolean) => value === true)) {
throw new Error(`Expected all remaining accounts to be read-only`);
}

if (sourceAddress !== null) {
Expand Down
Loading