Skip to content

Commit

Permalink
feat: SMR-3051 return withdrawal state (#2087)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcgcyx authored Aug 21, 2024
1 parent 5c06f0e commit 12e2a33
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 15 deletions.
8 changes: 4 additions & 4 deletions packages/internal/bridge/sdk/src/lib/tenderly.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ function generateAxiosData(
return {
jsonrpc: '2.0',
id: 0,
method: 'tenderly_estimateGasBundle',
method: 'tenderly_simulateBundle',
params: [
simulations,
'latest',
Expand Down Expand Up @@ -89,7 +89,7 @@ describe('Tenderly Utils', () => {

mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse });

const result = await submitTenderlySimulations(chainId, simulations);
const result = (await submitTenderlySimulations(chainId, simulations)).gas;

expect(result.length).toEqual(1);
expect(result[0]).toEqual(expectedResponse.result[0].gasUsed);
Expand Down Expand Up @@ -128,7 +128,7 @@ describe('Tenderly Utils', () => {

mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse });

const result = await submitTenderlySimulations(chainId, simulations);
const result = (await submitTenderlySimulations(chainId, simulations)).gas;

expect(result.length).toEqual(2);
expect(result[0]).toEqual(expectedResponse.result[0].gasUsed);
Expand Down Expand Up @@ -176,7 +176,7 @@ describe('Tenderly Utils', () => {

mockedAxios.post.mockResolvedValueOnce({ data: expectedResponse });

const result = await submitTenderlySimulations(chainId, simulations, stateObjects);
const result = (await submitTenderlySimulations(chainId, simulations, stateObjects)).gas;

expect(result.length).toEqual(2);
expect(result[0]).toEqual(expectedResponse.result[0].gasUsed);
Expand Down
60 changes: 55 additions & 5 deletions packages/internal/bridge/sdk/src/lib/tenderly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import axios, { AxiosResponse } from 'axios';
import { BridgeError, BridgeErrorType } from '../errors';
import { TenderlySimulation } from '../types/tenderly';
import { TenderlySimulation, TenderlyResult } from '../types/tenderly';
import { getTenderlyEndpoint } from './utils';

// In the Tenderly API, state objects are mapping of contract address -> "stateDiff" -> slot -> value
Expand All @@ -17,6 +17,23 @@ export type StateDiff = {
value: string;
};

type Input = {
name: string;
value: string | boolean;
};

type Event = {
name: string;
inputs: Array<Input>;
};

type Trace = {
method: string;
output: string | number;
};

const THRESHOLD_SELECTOR = '0x84a3291a0';

/**
* We want to convert a StateObject type to the following format (Record<string, Record<string, Record<string, string>>>):
* @example An example of a state object that changes the state at slot 0xe1b959...2585e to 1 at address 0xe43215...8E31:
Expand Down Expand Up @@ -60,7 +77,7 @@ export async function submitTenderlySimulations(
chainId: string,
simulations: Array<TenderlySimulation>,
stateObjects?: StateObject[],
): Promise<Array<number>> {
): Promise<TenderlyResult> {
let axiosResponse: AxiosResponse;
const tenderlyAPI = getTenderlyEndpoint(chainId);
const state_objects = stateObjects ? unwrapStateObjects(stateObjects) : undefined;
Expand All @@ -70,7 +87,7 @@ export async function submitTenderlySimulations(
{
jsonrpc: '2.0',
id: 0,
method: 'tenderly_estimateGasBundle',
method: 'tenderly_simulateBundle',
params: [
simulations,
'latest',
Expand Down Expand Up @@ -103,7 +120,14 @@ export async function submitTenderlySimulations(
}

const gas: Array<number> = [];
let delayWithdrawalLargeAmount: boolean = false;
let delayWithdrawalUnknownToken: boolean = false;
let withdrawalQueueActivated: boolean = false;
let largeTransferThresholds: number = 0;
let skipReadOperation = false;

// Check if simulations are for token withdrawal
const withdrawal = simulations.find((e: TenderlySimulation) => e.data?.startsWith(THRESHOLD_SELECTOR)) !== undefined;
for (let i = 0; i < simResults.length; i++) {
if (simResults[i].error) {
throw new BridgeError(
Expand All @@ -117,8 +141,34 @@ export async function submitTenderlySimulations(
BridgeErrorType.TENDERLY_GAS_ESTIMATE_FAILED,
);
}
gas.push(simResults[i].gasUsed);
// Attempt to extract event.
if (withdrawal && simResults[i].logs !== undefined) {
const event = simResults[i].logs.find((e: Event) => e.name === 'QueuedWithdrawal');
if (event !== undefined) {
const inputs: Map<string, string | boolean> = new Map(event.inputs.map((c: Input) => [c.name, c.value]));
delayWithdrawalLargeAmount = inputs.get('delayWithdrawalLargeAmount') as boolean || false;
delayWithdrawalUnknownToken = inputs.get('delayWithdrawalUnknownToken') as boolean || false;
withdrawalQueueActivated = inputs.get('withdrawalQueueActivated') as boolean || false;
}
}
// Check read operation.
if (withdrawal && simResults[i].trace !== undefined) {
const trace: Trace = simResults[i].trace.find((e: Trace) => e.method === 'largeTransferThresholds');
if (trace !== undefined) {
largeTransferThresholds = trace.output as number;
skipReadOperation = true;
}
}
if (!skipReadOperation) {
gas.push(simResults[i].gasUsed);
}
}

return gas;
return {
gas,
delayWithdrawalLargeAmount,
delayWithdrawalUnknownToken,
withdrawalQueueActivated,
largeTransferThresholds,
};
}
37 changes: 31 additions & 6 deletions packages/internal/bridge/sdk/src/tokenBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
isWrappedIMX,
shouldBeDepositOrFinaliseWithdraw,
} from './lib/utils';
import { TenderlySimulation } from './types/tenderly';
import { TenderlyResult, TenderlySimulation } from './types/tenderly';
import { calculateGasFee } from './lib/gas';
import { createContract } from './contracts/createContract';
import { getWithdrawRootToken, genAxelarWithdrawPayload, genUniqueAxelarCommandId } from './lib/axelarUtils';
Expand Down Expand Up @@ -679,6 +679,10 @@ export class TokenBridge {
contractToApprove,
unsignedApprovalTx,
unsignedBridgeTx,
delayWithdrawalLargeAmount: null,
delayWithdrawalUnknownToken: null,
withdrawalQueueActivated: null,
largeTransferThresholds: null,
};
}

Expand All @@ -690,7 +694,7 @@ export class TokenBridge {
amount: ethers.BigNumber,
gasMultiplier: number | string,
): Promise<BridgeBundledTxResponse> {
const [allowance, feeData, rootGas] = await Promise.all([
const [allowance, feeData, tenderlyRes] = await Promise.all([
this.getAllowance(direction, token, sender),
this.config.childProvider.getFeeData(),
await this.getDynamicWithdrawGasRootChain(
Expand All @@ -701,6 +705,7 @@ export class TokenBridge {
amount,
),
]);
const rootGas = tenderlyRes.gas[0];
// Get axelar fee
const axelarFee = await this.getAxelarFee(
this.config.bridgeInstance.childChainID,
Expand Down Expand Up @@ -770,6 +775,10 @@ export class TokenBridge {
contractToApprove,
unsignedApprovalTx,
unsignedBridgeTx,
delayWithdrawalLargeAmount: tenderlyRes.delayWithdrawalLargeAmount,
delayWithdrawalUnknownToken: tenderlyRes.delayWithdrawalUnknownToken,
withdrawalQueueActivated: tenderlyRes.withdrawalQueueActivated,
largeTransferThresholds: tenderlyRes.largeTransferThresholds,
};
}

Expand Down Expand Up @@ -828,7 +837,7 @@ export class TokenBridge {
});

// TODO this specific branch does not have tests written
const gas = await submitTenderlySimulations(sourceChainId, simulations);
const { gas } = await submitTenderlySimulations(sourceChainId, simulations);
const tenderlyGasEstimatesRes = {} as DynamicGasEstimatesResponse;
if (gas.length === 1) {
tenderlyGasEstimatesRes.approvalGas = 0;
Expand All @@ -848,7 +857,7 @@ export class TokenBridge {
recipient: string,
token: FungibleToken,
amount: ethers.BigNumber,
): Promise<number> {
): Promise<TenderlyResult> {
const rootToken = await getWithdrawRootToken(token, destinationChainId, this.config.childProvider);
const payload = genAxelarWithdrawPayload(
rootToken,
Expand Down Expand Up @@ -893,6 +902,23 @@ export class TokenBridge {
data: executeData,
}];

// Read large transfer threshold for given token
const bridgeAddress = this.config.bridgeContracts.rootERC20BridgeFlowRate;
const bridgeContract = await createContract(
bridgeAddress,
ROOT_ERC20_BRIDGE_FLOW_RATE,
this.config.rootProvider,
);
// Get current bucket state
const readData = await withBridgeError<string>(async () => bridgeContract.interface.encodeFunctionData(
'largeTransferThresholds',
[rootToken],
), BridgeErrorType.INTERNAL_ERROR);
simulations.push({
from: sender,
to: bridgeAddress,
data: readData,
});
const stateObject: StateObject = {
contractAddress: axelarGateway,
stateDiff: {
Expand All @@ -901,8 +927,7 @@ export class TokenBridge {
},
};

const gas = await submitTenderlySimulations(destinationChainId, simulations, [stateObject]);
return gas[0];
return await submitTenderlySimulations(destinationChainId, simulations, [stateObject]);
}

private async getAllowance(direction: BridgeDirection, token: string, sender: string): Promise<ethers.BigNumber> {
Expand Down
8 changes: 8 additions & 0 deletions packages/internal/bridge/sdk/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,20 @@ export interface BridgeBundledTxRequest {
* @property {ethers.providers.TransactionRequest | null} unsignedApprovalTx - The unsigned transaction for the token approval, or null
* if no approval is required.
* @property {ethers.providers.TransactionRequest} unsignedBridgeTx - The unsigned transaction for the deposit / withdrawal.
* @property {boolean | null} delayWithdrawalLargeAmount - If withdrawal gets queued due to large amount.
* @property {boolean | null} delayWithdrawalUnknownToken - If withdrawal gets queued due to unknown token.
* @property {boolean | null} withdrawalQueueActivated - If withdrawal gets queued due to activated queue.
* @property {number | null} largeTransferThresholds - The configured large transfer threshold for given withdrawal token.
*/
export interface BridgeBundledTxResponse {
feeData: BridgeFeeResponse,
contractToApprove: string | null,
unsignedApprovalTx: ethers.providers.TransactionRequest | null;
unsignedBridgeTx: ethers.providers.TransactionRequest;
delayWithdrawalLargeAmount: boolean | null;
delayWithdrawalUnknownToken: boolean | null;
withdrawalQueueActivated: boolean | null;
largeTransferThresholds: number | null;
}

/**
Expand Down
8 changes: 8 additions & 0 deletions packages/internal/bridge/sdk/src/types/tenderly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ export type TenderlySimulation = {
data?: string;
value?: string;
};

export type TenderlyResult = {
gas: Array<number>;
delayWithdrawalLargeAmount: boolean;
delayWithdrawalUnknownToken: boolean;
withdrawalQueueActivated: boolean;
largeTransferThresholds: number;
};

0 comments on commit 12e2a33

Please sign in to comment.