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 Elections #5355

Merged
merged 66 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
44bc3b8
feat: solana vault swaps elections
ramizhasan111 Oct 24, 2024
be5f075
feat: account closing tracking in elections
ramizhasan111 Oct 25, 2024
b9a66db
chore: clippy
ramizhasan111 Oct 25, 2024
b9ac4db
chore: minor
ramizhasan111 Oct 25, 2024
534d1df
fix: consensus
ramizhasan111 Oct 28, 2024
1e8f904
chore: minor
ramizhasan111 Oct 28, 2024
a284434
chore: minor rename
ramizhasan111 Oct 28, 2024
65ca599
Feat: engine logic to witness solana program swaps (#5313)
albert-llimos Oct 29, 2024
c888544
feat: return swap account along with swap details
ramizhasan111 Oct 29, 2024
a5eb16b
refactor: use election properties
ramizhasan111 Oct 29, 2024
687f650
feat: engine
ramizhasan111 Oct 30, 2024
84cd7da
feat: vault swap details
ramizhasan111 Oct 30, 2024
24c5a6d
chore: bouncer solana program swaps (#5318)
albert-llimos Oct 16, 2024
33da84b
Fixed bouncer
syan095 Oct 29, 2024
a8c60a0
chore: fix issues and renamings
albert-llimos Oct 31, 2024
b3d4fa3
chore: get block number from runtime
ramizhasan111 Oct 31, 2024
a565639
chore: vault_swap_request remove origin
ramizhasan111 Oct 31, 2024
0c6de22
fix: add is_vote_desired()
ramizhasan111 Oct 31, 2024
2965d78
fix: clippy, tests, benchmarks
ramizhasan111 Oct 31, 2024
5d23dbf
Feat: Expand Solana Vault Swap functionality and fix bouncer (#5375)
albert-llimos Nov 1, 2024
4d8af61
fix: BenchmarkValue shit, clippy and election order
ramizhasan111 Nov 1, 2024
a2c8bc6
feat: is_vote_needed
ramizhasan111 Nov 1, 2024
8758806
Test: bouncer solana event closure (#5378)
albert-llimos Nov 5, 2024
a96b1dd
Chore: refactor Solana program idls (#5381)
albert-llimos Nov 5, 2024
178cd10
chore: address comments
ramizhasan111 Nov 5, 2024
eef290e
chore: improve comment
albert-llimos Nov 5, 2024
d6f4f17
chore: nit
albert-llimos Nov 5, 2024
a403988
chore: nit CcmCfParameters
albert-llimos Nov 5, 2024
dc66695
chore: remove unnecessary comment
albert-llimos Nov 5, 2024
eceba13
chore: return hashshet in get_swap_endpoint_data
albert-llimos Nov 5, 2024
4927a01
chore: reduce duplication of data removing unnecessary hashet
albert-llimos Nov 5, 2024
ce0ec19
fix: filter out errored rpc calls
ramizhasan111 Nov 5, 2024
9783c86
chore: workaround for bouncer lint
albert-llimos Nov 6, 2024
9efd869
chore: address comments
albert-llimos Nov 6, 2024
18f793f
chore: address comments
ramizhasan111 Nov 7, 2024
a16fdbd
chore: rename file
ramizhasan111 Nov 7, 2024
8329c22
chore: make src_token safer, add unwrap()
ramizhasan111 Nov 7, 2024
51ed806
chore: pull out common code
ramizhasan111 Nov 7, 2024
3896a89
fix: chore: address comments
ramizhasan111 Nov 8, 2024
f1bf1e5
chore: address comments
ramizhasan111 Nov 8, 2024
c6811f8
chore: rebase related fixes, integrating latest features from main
ramizhasan111 Nov 14, 2024
f53e86c
chore: fixes after merge
albert-llimos Nov 14, 2024
d7c280e
chore: merge latest main
albert-llimos Nov 14, 2024
5788216
feat: update engine broker and affiliate fees
albert-llimos Nov 14, 2024
89fa7c5
chore: update dummy hardcoded test cf_parameters
albert-llimos Nov 14, 2024
39381d1
chore: refactor bouncer vault swaps
albert-llimos Nov 15, 2024
7f5ae33
chore: fix versioned decoding
albert-llimos Nov 15, 2024
4033b59
chore: fix bouncer lint
albert-llimos Nov 15, 2024
0eee9b7
feat: close accounts even for invalid swaps (#5405)
ramizhasan111 Nov 15, 2024
e1312d1
test: solana vault swap electoral system tests (#5382)
ramizhasan111 Nov 20, 2024
850eba7
fix: merge fixes
ramizhasan111 Nov 20, 2024
21a1cf4
feat: additional test for invalid swap case
ramizhasan111 Nov 20, 2024
2ca01b0
chore: minor
ramizhasan111 Nov 20, 2024
7eeb355
fix: remove expects
ramizhasan111 Nov 20, 2024
7027910
chore: clearer error message
dandanlen Nov 20, 2024
be8c5a1
feat: migration
ramizhasan111 Nov 21, 2024
aa607bd
chore: test upgrade
albert-llimos Nov 21, 2024
e6ba51f
Merge branch 'main' into feat/solana-vault-swaps-election
dandanlen Nov 21, 2024
9bda265
chore: update migration versions
dandanlen Nov 21, 2024
284e81e
chore: update version
ramizhasan111 Nov 21, 2024
b99e051
Merge branch 'main' into feat/solana-vault-swaps-election
kylezs Nov 25, 2024
bafc496
fix: print pre and post upgrade logs in upgrade-test
kylezs Nov 25, 2024
f52d20c
Feat: Use TransactionInId for Solana Vault Swaps (#5442)
albert-llimos Nov 26, 2024
0f0746c
chore: update affiliate_fee type
albert-llimos Nov 26, 2024
bcabca3
chore: restore upgrade flag
albert-llimos Nov 26, 2024
6854f90
chore: merge from main and fix clippy
albert-llimos Nov 27, 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 .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
CF_ETH_CONTRACT_ABI_ROOT = { value = "contract-interfaces/eth-contract-abis", relative = true }
CF_ETH_CONTRACT_ABI_TAG = "v1.1.2"
CF_SOL_PROGRAM_IDL_ROOT = { value = "contract-interfaces/sol-program-idls", relative = true }
CF_SOL_PROGRAM_IDL_TAG = "v1.0.0"
CF_SOL_PROGRAM_IDL_TAG = "v1.0.0-swap-endpoint"
CF_ARB_CONTRACT_ABI_ROOT = { value = "contract-interfaces/arb-contract-abis", relative = true }
CF_TEST_CONFIG_ROOT = { value = "engine/config/testing", relative = true }

Expand Down
20 changes: 16 additions & 4 deletions .github/workflows/upgrade-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ on:
env:
FORCE_COLOR: 1
SOLANA_VERSION: v1.18.17
SOLANA_PROGRAMS_VERSION: v1.0.0-swap-endpoint
NODE_COUNT: "1-node"

permissions:
Expand Down Expand Up @@ -172,7 +173,20 @@ jobs:
echo "/usr/lib after copy of .so files"
ls -l /usr/lib
touch ./localnet/.setup_complete
# TODO: This is a temporary fix to allow the localnet docker-compose.yml from an older commit to run the latest solana programs version. Remove after 1.8 is released.
sed -i 's|ghcr.io/chainflip-io/solana-localnet-ledger:v[0-9.]*|ghcr.io/chainflip-io/solana-localnet-ledger:${{ env.SOLANA_PROGRAMS_VERSION }}|g' localnet/docker-compose.yml
./localnet/manage.sh
git reset --hard

# TODO: This is a temporary workaround to insert the 1.8 image nonces. Remove after 1.8 is released.
- name: Nonce workaround
if: inputs.run-job
run: |
git checkout ${{ github.sha }}
git rev-parse HEAD
cd bouncer
pnpm install --frozen-lockfile
./shared/force_sol_nonces.ts

- name: Run bouncer on upgrade-from version 🙅‍♂️
if: inputs.run-job
Expand All @@ -183,8 +197,6 @@ jobs:
cd bouncer
pnpm install --frozen-lockfile
./run.sh
# TODO: Temporary to discard the fixes above
git reset --hard

- name: Upgrade network 🚀
if: inputs.run-job
Expand Down Expand Up @@ -229,13 +241,13 @@ jobs:
# In the case of a compatible upgrade, we don't expect any logs here
continue-on-error: true
run: |
cat /tmp/chainflip/*/start-all-engines-pre-upgrade.*log
cat /tmp/chainflip/*/chainflip-engine-pre-upgrade.*log

- name: Print new post-upgrade chainflip-engine logs 🚗
if: always()
continue-on-error: true
run: |
cat /tmp/chainflip/*/start-all-engines-post-upgrade.*log
cat /tmp/chainflip/*/chainflip-engine-post-upgrade.*log

- name: Print chainflip-node logs 📡
if: always()
Expand Down
1 change: 0 additions & 1 deletion bouncer/cf-abis

This file was deleted.

2 changes: 1 addition & 1 deletion bouncer/shared/contract_interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function loadContractCached(abiPath: string) {
};
}
const CF_ETH_CONTRACT_ABI_TAG = 'v1.1.2';
const CF_SOL_PROGRAM_IDL_TAG = 'v1.0.0';
const CF_SOL_PROGRAM_IDL_TAG = 'v1.0.0-swap-endpoint';
export const getErc20abi = loadContractCached(
'../contract-interfaces/eth-contract-abis/IERC20.json',
);
Expand Down
239 changes: 6 additions & 233 deletions bouncer/shared/evm_vault_swap.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import * as anchor from '@coral-xyz/anchor';
// import NodeWallet from '@coral-xyz/anchor/dist/cjs/nodewallet';

import {
InternalAsset as Asset,
executeSwap,
Expand All @@ -12,40 +9,22 @@ 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 Keyring from '../polkadot/keyring';
import {
observeBalanceIncrease,
getContractAddress,
observeCcmReceived,
amountToFineAmount,
defaultAssetAmounts,
chainFromAsset,
assetDecimals,
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(
export async function executeEvmVaultSwap(
sourceAsset: Asset,
destAsset: Asset,
destAddress: string,
Expand Down Expand Up @@ -81,7 +60,7 @@ export async function executeVaultSwap(
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(
await approveEvmTokenVault(
sourceAsset,
(BigInt(amountToFineAmount(amountToSwap, assetDecimals(sourceAsset))) * 100n).toString(),
evmWallet,
Expand Down Expand Up @@ -128,218 +107,12 @@ 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;
destAddress: string;
txHash: string;
};

export async function performVaultSwap(
export async function approveEvmTokenVault(
sourceAsset: Asset,
destAsset: Asset,
destAddress: string,
swapTag = '',
messageMetadata?: CcmDepositMetadata,
swapContext?: SwapContext,
log = true,
amount?: string,
boostFeeBps?: number,
fillOrKillParams?: FillOrKillParamsX128,
dcaParams?: DcaParams,
): Promise<VaultSwapParams> {
const tag = swapTag ?? '';
const amountToSwap = amount ?? defaultAssetAmounts(sourceAsset);
const srcChain = chainFromAsset(sourceAsset);

try {
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) {
console.log(`${tag} Old balance: ${oldBalance}`);
console.log(
`${tag} Executing (${sourceAsset}) vault swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`,
);
}

// 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, sourceAddress)
: Promise.resolve();

const [newBalance] = await Promise.all([
observeBalanceIncrease(destAsset, destAddress, oldBalance),
ccmEventEmitted,
]);
if (log) {
console.log(`${tag} Swap success! New balance: ${newBalance}!`);
}
swapContext?.updateStatus(swapTag, SwapStatus.Success);
return {
sourceAsset,
destAsset,
destAddress,
txHash,
};
} catch (err) {
console.error('err:', err);
swapContext?.updateStatus(swapTag, SwapStatus.Failure);
if (err instanceof Error) {
console.log(err.stack);
}
throw new Error(`${tag} ${err}`);
}
}
export async function approveTokenVault(sourceAsset: Asset, amount: string, wallet: HDNodeWallet) {
amount: string,
wallet: HDNodeWallet,
) {
if (!erc20Assets.includes(sourceAsset)) {
throw new Error(`Unsupported asset, not an ERC20: ${sourceAsset}`);
}
Expand Down
Loading
Loading