Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into chore/move-bidding-to…
Browse files Browse the repository at this point in the history
…-validator

* origin/main:
  fix: broker flakiness on bouncer CI (#4736)
  fix: migration for earned fees (#4733)
  feat: handle prewitness deposits (#4698)
  Update Sdk with Arbitrum support (#4720)
  chore: remove insta deprecated warning (#4734)
  refactor: move vanity names to account roles pallet (#4719)
  Arbitrum backup rpc (#4730)

# Conflicts:
#	state-chain/pallets/cf-validator/src/lib.rs
#	state-chain/pallets/cf-validator/src/mock.rs
#	state-chain/pallets/cf-validator/src/tests.rs
  • Loading branch information
syan095 committed Apr 8, 2024
2 parents 60a788e + 46a5d0a commit d2b34b8
Show file tree
Hide file tree
Showing 59 changed files with 3,457 additions and 581 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

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

12 changes: 5 additions & 7 deletions api/lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use cf_chains::{
};
use cf_primitives::{AccountRole, Asset, BasisPoints, ChannelId, SemVer};
use futures::FutureExt;
use pallet_cf_account_roles::MAX_LENGTH_FOR_VANITY_NAME;
use pallet_cf_governance::ExecutionMode;
use pallet_cf_validator::MAX_LENGTH_FOR_VANITY_NAME;
use serde::{Deserialize, Serialize};
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
use sp_consensus_grandpa::AuthorityId as GrandpaId;
Expand Down Expand Up @@ -283,13 +283,11 @@ pub trait OperatorApi: SignedExtrinsicApi + RotateSessionKeysApi + AuctionPhaseA
}

async fn set_vanity_name(&self, name: String) -> Result<()> {
if name.len() > MAX_LENGTH_FOR_VANITY_NAME {
bail!("Name too long. Max length is {} characters.", MAX_LENGTH_FOR_VANITY_NAME,);
}

let (tx_hash, ..) = self
.submit_signed_extrinsic(pallet_cf_validator::Call::set_vanity_name {
name: name.as_bytes().to_vec(),
.submit_signed_extrinsic(pallet_cf_account_roles::Call::set_vanity_name {
name: name.into_bytes().try_into().or_else(|_| {
bail!("Name too long. Max length is {} characters.", MAX_LENGTH_FOR_VANITY_NAME,)
})?,
})
.await
.until_in_block()
Expand Down
10 changes: 0 additions & 10 deletions bouncer/commands/go_bananas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,6 @@ export function assetFromStateChainAsset(
return (stateChainAsset.charAt(0) + stateChainAsset.slice(1).toLowerCase()) as Asset;
}

// TODO: Temporal workaround: To remove once SDK supports Arbitrum
if (stateChainAsset.chain === 'Arbitrum') {
if (stateChainAsset.asset === 'ETH') {
return 'ArbEth' as Asset;
}
if (stateChainAsset.asset === 'USDC') {
return 'ArbUsdc' as Asset;
}
}

return getInternalAsset(stateChainAsset);
}

Expand Down
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.3.0-alpha.2",
"@chainflip/cli": "1.3.0",
"@iarna/toml": "^2.2.5",
"@polkadot/api": "10.7.2",
"@polkadot/keyring": "12.2.1",
Expand Down
8 changes: 4 additions & 4 deletions bouncer/pnpm-lock.yaml

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

108 changes: 66 additions & 42 deletions bouncer/shared/contract_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import {
executeSwap,
ExecuteSwapParams,
approveVault,
Asset as SCAsset,
Chains,
} from '@chainflip/cli';
import { Wallet, getDefaultProvider } from 'ethers';
import { HDNodeWallet, Wallet, getDefaultProvider } from 'ethers';
import {
getChainflipApi,
observeBalanceIncrease,
Expand All @@ -15,37 +17,35 @@ import {
defaultAssetAmounts,
chainFromAsset,
getEvmEndpoint,
getWhaleMnemonic,
assetDecimals,
stateChainAssetFromAsset,
chainGasAsset,
} from './utils';
import { getNextEvmNonce } from './send_evm';
import { getBalance } from './get_balance';
import { CcmDepositMetadata } from '../shared/new_swap';
import { send } from './send';

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

export async function executeContractSwap(
srcAsset: Asset,
destAsset: Asset,
destAddress: string,
wallet: HDNodeWallet,
messageMetadata?: CcmDepositMetadata,
): ReturnType<typeof executeSwap> {
const srcChain = chainFromAsset(srcAsset);
const wallet = Wallet.fromPhrase(getWhaleMnemonic(srcChain)).connect(
getDefaultProvider(getEvmEndpoint(srcChain)),
);

const destChain = chainFromAsset(destAsset);

const nonce = await getNextEvmNonce(srcChain);
const networkOptions = {
signer: wallet,
network: 'localnet',
vaultContractAddress: getEvmContractAddress(srcChain, 'VAULT'),
srcTokenContractAddress: getEvmContractAddress(srcChain, srcAsset),
} as const;
const txOptions = {
nonce,
gasLimit: 200000n,
// This is run with fresh addresses to prevent nonce issues. Will be 1 for ERC20s.
gasLimit: srcChain === Chains.Arbitrum ? 10000000n : 200000n,
} as const;

const receipt = await executeSwap(
Expand Down Expand Up @@ -87,16 +87,50 @@ export async function performSwapViaContract(

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)));

try {
// Fund new key with native asset and asset to swap.
await send(chainGasAsset(srcChain), 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,
);
}

const oldBalance = await getBalance(destAsset, destAddress);
console.log(`${tag} Old balance: ${oldBalance}`);
console.log(
`${tag} Executing (${sourceAsset}) contract swap to(${destAsset}) ${destAddress}. Current balance: ${oldBalance}`,
);

// To uniquely identify the contractSwap, 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 executeContractSwap(sourceAsset, destAsset, destAddress, messageMetadata);
const receipt = await executeContractSwap(
sourceAsset,
destAsset,
destAddress,
wallet,
messageMetadata,
);
await observeEvent('swapping:SwapScheduled', api, (event) => {
if ('Vault' in event.data.origin) {
const sourceAssetMatches = sourceAsset === (event.data.sourceAsset as Asset);
Expand All @@ -115,7 +149,7 @@ export async function performSwapViaContract(
destAsset,
destAddress,
messageMetadata,
Wallet.fromPhrase(getWhaleMnemonic(chainFromAsset(sourceAsset))).address.toLowerCase(),
wallet.address.toLowerCase(),
)
: Promise.resolve();

Expand All @@ -138,38 +172,28 @@ 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(
srcAsset: 'Flip' | 'Usdc' | 'Usdt' | 'ArbUsdc',
amount: string,
) {
const chain = chainFromAsset(srcAsset as Asset);

const wallet = Wallet.fromPhrase(getWhaleMnemonic(chain)).connect(
getDefaultProvider(getEvmEndpoint(chain)),
);

// TODO: To remove when Arbitrum is supported in the SDK
if (chain !== 'Ethereum' || srcAsset === 'ArbUsdc') {
throw new Error('Arbitrum is not supported for token approvals');
}

await getNextEvmNonce(chain, (nextNonce) =>
approveVault(
{
amount,
srcChain: chain,
srcAsset: stateChainAssetFromAsset(srcAsset),
},
{
signer: wallet,
network: 'localnet',
vaultContractAddress: getEvmContractAddress(chain, 'VAULT'),
srcTokenContractAddress: getEvmContractAddress(chain, srcAsset),
},
{
nonce: nextNonce,
},
),
await approveVault(
{
amount,
srcChain: chain,
srcAsset: stateChainAssetFromAsset(srcAsset) as SCAsset,
},
{
signer: wallet,
network: 'localnet',
vaultContractAddress: getEvmContractAddress(chain, 'VAULT'),
srcTokenContractAddress: getEvmContractAddress(chain, srcAsset),
},
// This is run with fresh addresses to prevent nonce issues
{
nonce: 0,
},
);
}
13 changes: 4 additions & 9 deletions bouncer/shared/gaslimit_ccm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InternalAsset as Asset, InternalAssets as Assets, Chain } from '@chainf
import { newCcmMetadata, prepareSwap } from './swapping';
import {
chainFromAsset,
chainGasAsset,
getChainflipApi,
getEvmEndpoint,
observeBadEvents,
Expand Down Expand Up @@ -38,12 +39,6 @@ const GAS_PER_BYTE = 17;
const MIN_FEE: Record<string, number> = { Ethereum: 1000000000, Arbitrum: 100000000 };
const LOOP_TIMEOUT = 15;

const CCM_CHAINS_NATIVE_ASSETS: Record<string, Asset> = {
Ethereum: 'Eth',
Arbitrum: 'ArbEth',
// Solana: 'Sol',
};

function gasTestCcmMetadata(sourceAsset: Asset, gasToConsume: number, gasBudgetFraction?: number) {
const web3 = new Web3();

Expand Down Expand Up @@ -111,10 +106,10 @@ async function testGasLimitSwap(

let gasSwapScheduledHandle;

if (CCM_CHAINS_NATIVE_ASSETS[destChain] !== sourceAsset) {
if (chainGasAsset(destChain as Chain) !== sourceAsset) {
gasSwapScheduledHandle = observeSwapScheduled(
sourceAsset,
destChain === 'Ethereum' ? Assets.Eth : ('ArbEth' as Asset),
destChain === 'Ethereum' ? Assets.Eth : Assets.ArbEth,
channelId,
SwapType.CcmGas,
);
Expand Down Expand Up @@ -194,7 +189,7 @@ async function testGasLimitSwap(

let egressBudgetAmount;

if (CCM_CHAINS_NATIVE_ASSETS[destChain] === sourceAsset) {
if (chainGasAsset(destChain as Chain) === sourceAsset) {
egressBudgetAmount = messageMetadata.gasBudget;
} else {
const {
Expand Down
49 changes: 31 additions & 18 deletions bouncer/shared/new_swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,35 @@ export async function newSwap(
destAsset === 'Dot' ? decodeDotAddressForContract(destAddress) : destAddress;
const brokerUrl = process.env.BROKER_ENDPOINT || 'http://127.0.0.1:10997';

await broker.requestSwapDepositAddress(
{
srcAsset: stateChainAssetFromAsset(sourceAsset) as SCAsset,
destAsset: stateChainAssetFromAsset(destAsset) as SCAsset,
srcChain: chainFromAsset(sourceAsset),
destAddress: destinationAddress,
destChain: chainFromAsset(destAsset),
ccmMetadata: messageMetadata && {
message: messageMetadata.message as `0x${string}`,
gasBudget: messageMetadata.gasBudget.toString(),
},
},
{
url: brokerUrl,
commissionBps: brokerCommissionBps,
},
'backspin',
);
// If the dry_run of the extrinsic fails on the broker-api then it won't retry. So we retry here to
// avoid flakiness on CI.
let retryCount = 0;
while (retryCount < 20) {
try {
await broker.requestSwapDepositAddress(
{
srcAsset: stateChainAssetFromAsset(sourceAsset) as SCAsset,
destAsset: stateChainAssetFromAsset(destAsset) as SCAsset,
srcChain: chainFromAsset(sourceAsset),
destAddress: destinationAddress,
destChain: chainFromAsset(destAsset),
ccmMetadata: messageMetadata && {
message: messageMetadata.message as `0x${string}`,
gasBudget: messageMetadata.gasBudget.toString(),
},
},
{
url: brokerUrl,
commissionBps: brokerCommissionBps,
},
'backspin',
);
break; // Exit the loop on success
} catch (error) {
retryCount++;
console.error(
`Request swap deposit address for ${sourceAsset} attempt: ${retryCount} failed: ${error}`,
);
}
}
}
Loading

0 comments on commit d2b34b8

Please sign in to comment.