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: Solana Vault swaps (base PR) #5309

Merged
merged 25 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
21eec8c
chore: adding api call
albert-llimos Sep 30, 2024
532416f
chore: add swap endpoint accounts to solEnvApi
albert-llimos Oct 1, 2024
38b9307
chore: add swap endpoint contract
albert-llimos Oct 1, 2024
f21e379
chore: add swap endpoint initialization
albert-llimos Oct 1, 2024
9e0de52
chore: fix lint and initialization
albert-llimos Oct 1, 2024
8910e63
chore: update swap endpoint data account
albert-llimos Oct 1, 2024
be43033
chore: remove initialization as its already done in image
albert-llimos Oct 1, 2024
05bf121
chore: cleanup
albert-llimos Oct 1, 2024
903db1b
chore: restore ci
albert-llimos Oct 2, 2024
f4b573c
chore: remove unnecessary TODOs
albert-llimos Oct 8, 2024
833de64
Added storage migration for Environment pallet.
syan095 Oct 11, 2024
81558b0
chore: address comment
albert-llimos Oct 16, 2024
7b94578
chore: bouncer solana program swaps (#5318)
albert-llimos Oct 16, 2024
a6e2c9a
Moved Environment pallet's migration from Runtime into Pallet
syan095 Oct 28, 2024
af8b520
Merge remote-tracking branch 'origin/main' into feat/solana-program-s…
syan095 Oct 28, 2024
7136f6d
Fixed bouncer
syan095 Oct 29, 2024
f8d3821
Corrected placeholder migration version for the broadcaster pallet
syan095 Oct 29, 2024
f97fefb
chore: merge from main and fix conflicts
albert-llimos Oct 31, 2024
144852f
chore: fix lint
albert-llimos Oct 31, 2024
74b4b87
chore: merge from main, fix merge conflicts
albert-llimos Nov 11, 2024
d3f845c
chore: run upgrade test
albert-llimos Nov 11, 2024
87e8d80
chore: use Parity VersionedMigration
albert-llimos Nov 11, 2024
ef4eb94
chore: merge from main
albert-llimos Nov 11, 2024
b8c714d
Fixed try-runtime error
syan095 Nov 11, 2024
e3add5b
chore: not run upgrade test
albert-llimos Nov 12, 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
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion bouncer/commands/setup_vaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ async function main(): Promise<void> {

// Confirmation
console.log('Waiting for new epoch...');
await observeEvent('validator:NewEpoch');
await observeEvent('validator:NewEpoch').event;

console.log('=== New Epoch ===');
console.log('=== Vault Setup completed ===');
Expand Down
3 changes: 3 additions & 0 deletions bouncer/shared/contract_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ export const getSolanaVaultIdl = loadContractCached(
export const getCfTesterIdl = loadContractCached(
`../contract-interfaces/sol-program-idls/${CF_SOL_PROGRAM_IDL_TAG}/cf_tester.json`,
);
export const getSolanaSwapEndpointIdl = loadContractCached(
`../contract-interfaces/sol-program-idls/${CF_SOL_PROGRAM_IDL_TAG}/swap_endpoint.json`,
);
193 changes: 168 additions & 25 deletions bouncer/shared/evm_vault_swap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as anchor from '@coral-xyz/anchor';
// import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet';

import {
InternalAsset as Asset,
executeSwap,
Expand All @@ -9,6 +12,8 @@ import {
} from '@chainflip/cli';
import { HDNodeWallet } from 'ethers';
import { randomBytes } from 'crypto';
import { PublicKey, sendAndConfirmTransaction, Keypair } from '@solana/web3.js';
import { getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import {
observeBalanceIncrease,
getContractAddress,
Expand All @@ -20,11 +25,23 @@ import {
stateChainAssetFromAsset,
createEvmWalletAndFund,
newAddress,
evmChains,
getSolWhaleKeyPair,
getSolConnection,
} from './utils';
import { getBalance } from './get_balance';
import { CcmDepositMetadata, DcaParams, FillOrKillParamsX128 } from './new_swap';
import { SwapContext, SwapStatus } from './swap_context';

import VaultIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/vault.json';
import SwapEndpointIdl from '../../contract-interfaces/sol-program-idls/v1.0.0/swap_endpoint.json';

import { SwapEndpoint } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/swap_endpoint';
import { Vault } from '../../contract-interfaces/sol-program-idls/v1.0.0/types/vault';

// Workaround because of anchor issue
const { BN } = anchor;

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

export async function executeVaultSwap(
Expand Down Expand Up @@ -100,6 +117,120 @@ export async function executeVaultSwap(

return receipt;
}
// Temporary before the SDK implements this.
export async function executeSolContractSwap(
srcAsset: Asset,
destAsset: Asset,
destAddress: string,
messageMetadata?: CcmDepositMetadata,
) {
const destChain = chainFromAsset(destAsset);

// const solanaSwapEndpointId = new PublicKey(getContractAddress('Solana', 'SWAP_ENDPOINT'));
const solanaVaultDataAccount = new PublicKey(getContractAddress('Solana', 'DATA_ACCOUNT'));
const swapEndpointDataAccount = new PublicKey(
getContractAddress('Solana', 'SWAP_ENDPOINT_DATA_ACCOUNT'),
);
const whaleKeypair = getSolWhaleKeyPair();

// We should just be able to do this instead but it's not working...
// const wallet = new NodeWallet(whaleKeypair);
// const provider = new anchor.AnchorProvider(connection, wallet, {
// commitment: 'processed',
// });
// const cfSwapEndpointProgram = new anchor.Program<SwapEndpoint>(SwapEndpointIdl, provider);
// const vaultProgram = new anchor.Program<Vault>(VaultIdl, provider);

// The current workaround requires having the wallet in a id.json and then set the ANCHOR_WALLET env.
// TODO: Depending on how the SDK is implemented we can remove this.
process.env.ANCHOR_WALLET = 'shared/solana_keypair.json';

const connection = getSolConnection();
const cfSwapEndpointProgram = new anchor.Program<SwapEndpoint>(SwapEndpointIdl as SwapEndpoint);
const vaultProgram = new anchor.Program<Vault>(VaultIdl as Vault);

const newEventAccountKeypair = Keypair.generate();
const fetchedDataAccount = await vaultProgram.account.dataAccount.fetch(solanaVaultDataAccount);
const aggKey = fetchedDataAccount.aggKey;

const tx =
srcAsset === 'Sol'
? await cfSwapEndpointProgram.methods
.xSwapNative({
amount: new BN(
amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)),
),
dstChain: Number(destChain),
dstAddress: Buffer.from(destAddress),
dstToken: Number(stateChainAssetFromAsset(destAsset)),
ccmParameters: messageMetadata
? {
message: Buffer.from(messageMetadata.message.slice(2), 'hex'),
gasAmount: new BN(messageMetadata.gasBudget),
}
: null,
// TODO: Encode cfParameters from ccmAdditionalData and other vault swap parameters
cfParameters: Buffer.from(messageMetadata?.ccmAdditionalData?.slice(2) ?? '', 'hex'),
})
.accountsPartial({
dataAccount: solanaVaultDataAccount,
aggKey,
from: whaleKeypair.publicKey,
eventDataAccount: newEventAccountKeypair.publicKey,
swapEndpointDataAccount,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([whaleKeypair, newEventAccountKeypair])
.transaction()
: await cfSwapEndpointProgram.methods
.xSwapToken({
amount: new BN(
amountToFineAmount(defaultAssetAmounts(srcAsset), assetDecimals(srcAsset)),
),
dstChain: Number(destChain),
dstAddress: Buffer.from(destAddress),
dstToken: Number(stateChainAssetFromAsset(destAsset)),
ccmParameters: messageMetadata
? {
message: Buffer.from(messageMetadata.message.slice(2), 'hex'),
gasAmount: new BN(messageMetadata.gasBudget),
}
: null,
// TODO: Encode cfParameters from ccmAdditionalData and other vault swap parameters
cfParameters: Buffer.from(messageMetadata?.ccmAdditionalData?.slice(2) ?? '', 'hex'),
decimals: assetDecimals(srcAsset),
})
.accountsPartial({
dataAccount: solanaVaultDataAccount,
tokenVaultAssociatedTokenAccount: new PublicKey(
getContractAddress('Solana', 'TOKEN_VAULT_ATA'),
),
from: whaleKeypair.publicKey,
fromTokenAccount: getAssociatedTokenAddressSync(
new PublicKey(getContractAddress('Solana', 'SolUsdc')),
whaleKeypair.publicKey,
false,
),
eventDataAccount: newEventAccountKeypair.publicKey,
swapEndpointDataAccount,
tokenSupportedAccount: new PublicKey(
getContractAddress('Solana', 'SolUsdcTokenSupport'),
),
tokenProgram: TOKEN_PROGRAM_ID,
mint: new PublicKey(getContractAddress('Solana', 'SolUsdc')),
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([whaleKeypair, newEventAccountKeypair])
.transaction();
const txHash = await sendAndConfirmTransaction(connection, tx, [
whaleKeypair,
newEventAccountKeypair,
]);

console.log('tx', txHash);
return txHash;
}

export type VaultSwapParams = {
sourceAsset: Asset;
destAsset: Asset;
Expand All @@ -122,11 +253,21 @@ export async function performVaultSwap(
): Promise<VaultSwapParams> {
const tag = swapTag ?? '';
const amountToSwap = amount ?? defaultAssetAmounts(sourceAsset);
const srcChain = chainFromAsset(sourceAsset);

try {
// Generate a new wallet for each vault swap to prevent nonce issues when running in parallel
// with other swaps via deposit channels.
const wallet = await createEvmWalletAndFund(sourceAsset);
let wallet;
let txHash: string;
let sourceAddress: string;

if (evmChains.includes(srcChain)) {
// Generate a new wallet for each vault swap to prevent nonce issues when running in parallel
// with other swaps via deposit channels.
wallet = await createEvmWalletAndFund(sourceAsset);
sourceAddress = wallet!.address.toLowerCase();
} else {
sourceAddress = getSolWhaleKeyPair().publicKey.toBase58();
}

const oldBalance = await getBalance(destAsset, destAddress);
if (log) {
Expand All @@ -136,30 +277,32 @@ export async function performVaultSwap(
);
}

// To uniquely identify the VaultSwap, we need to use the TX hash. This is only known
// after sending the transaction, so we send it first and observe the events afterwards.
// There are still multiple blocks of safety margin inbetween before the event is emitted
const receipt = await executeVaultSwap(
sourceAsset,
destAsset,
destAddress,
messageMetadata,
amountToSwap,
boostFeeBps,
fillOrKillParams,
dcaParams,
wallet,
);
// TODO: Temporary before the SDK implements this.
if (evmChains.includes(srcChain)) {
// To uniquely identify the VaultSwap, we need to use the TX hash. This is only known
// after sending the transaction, so we send it first and observe the events afterwards.
// There are still multiple blocks of safety margin inbetween before the event is emitted
const receipt = await executeVaultSwap(
sourceAsset,
destAsset,
destAddress,
messageMetadata,
amountToSwap,
boostFeeBps,
fillOrKillParams,
dcaParams,
wallet,
);
txHash = receipt.hash;
sourceAddress = wallet!.address.toLowerCase();
} else {
txHash = await executeSolContractSwap(sourceAsset, destAsset, destAddress, messageMetadata);
sourceAddress = getSolWhaleKeyPair().publicKey.toBase58();
}
swapContext?.updateStatus(swapTag, SwapStatus.VaultContractExecuted);

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

const [newBalance] = await Promise.all([
Expand All @@ -174,7 +317,7 @@ export async function performVaultSwap(
sourceAsset,
destAsset,
destAddress,
txHash: receipt.hash,
txHash,
};
} catch (err) {
console.error('err:', err);
Expand Down
5 changes: 1 addition & 4 deletions bouncer/shared/initialize_new_chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,7 @@ export async function initializeSolanaPrograms(solClient: Connection, solKey: st

const solUsdcMintPubkey = new PublicKey(getContractAddress('Solana', 'SolUsdc'));

const [tokenSupportedAccount] = PublicKey.findProgramAddressSync(
[Buffer.from('supported_token'), solUsdcMintPubkey.toBuffer()],
solanaVaultProgramId,
);
const tokenSupportedAccount = new PublicKey(getContractAddress('Solana', 'SolUsdcTokenSupport'));

tx.add(
new TransactionInstruction({
Expand Down
6 changes: 6 additions & 0 deletions bouncer/shared/solana_keypair.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[
6, 151, 150, 20, 145, 210, 176, 113, 98, 200, 192, 80, 73, 63, 133, 232, 208, 124, 81, 213, 117,
199, 196, 243, 219, 33, 79, 217, 157, 69, 205, 140, 247, 157, 94, 2, 111, 18, 237, 198, 68, 58,
83, 75, 44, 221, 80, 114, 35, 57, 137, 180, 21, 215, 89, 101, 115, 231, 67, 243, 229, 179, 134,
251
]
13 changes: 13 additions & 0 deletions bouncer/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const brokerMutex = new Mutex();
export const snowWhiteMutex = new Mutex();

export const ccmSupportedChains = ['Ethereum', 'Arbitrum', 'Solana'] as Chain[];
export const evmChains = ['Ethereum', 'Arbitrum'] as Chain[];

export type Asset = SDKAsset;
export type Chain = SDKChain;
Expand Down Expand Up @@ -114,14 +115,26 @@ export function getContractAddress(chain: Chain, contract: string): string {
return '8inHGLHXegST3EPLcpisQe9D1hDT9r7DJjS395L3yuYf';
case 'TOKEN_VAULT_PDA':
return '7B13iu7bUbBX88eVBqTZkQqrErnTMazPmGLdE5RqdyKZ';
case 'TOKEN_VAULT_ATA':
return '9CGLwcPknpYs3atgwtjMX7RhgvBgaqK8wwCvXnmjEoL9';
case 'DATA_ACCOUNT':
return 'BttvFNSRKrkHugwDP6SpnBejCKKskHowJif1HGgBtTfG';
case 'SolUsdc':
return process.env.SOL_USDC_ADDRESS ?? '24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p';
case 'SolUsdcTokenSupport':
return PublicKey.findProgramAddressSync(
[
Buffer.from('supported_token'),
new PublicKey(getContractAddress('Solana', 'SolUsdc')).toBuffer(),
],
new PublicKey(getContractAddress('Solana', 'VAULT')),
)[0].toBase58();
case 'CFTESTER':
return '8pBPaVfTAcjLeNfC187Fkvi9b1XEFhRNJ95BQXXVksmH';
case 'SWAP_ENDPOINT':
return '35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT';
case 'SWAP_ENDPOINT_DATA_ACCOUNT':
return '2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9';
default:
throw new Error(`Unsupported contract: ${contract}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ gh release download \
unzip -u ${ZIP_FILE} \
'vault.json' \
'cf_tester.json' \
'swap_endpoint.json' \
-d $TARGET_DIR

rm ${ZIP_FILE}
Loading
Loading