From de14541ad4a99854c42ea4663901e219622e7b51 Mon Sep 17 00:00:00 2001 From: Kelvin Fichter <kelvin@optimism.io> Date: Mon, 24 Jan 2022 12:29:42 -0500 Subject: [PATCH] feat(sdk): implement L2 message gas estimation --- packages/sdk/src/cross-chain-provider.ts | 22 ++++- .../src/interfaces/cross-chain-provider.ts | 9 +- .../sdk/test/cross-chain-provider.spec.ts | 89 ++++++++++++++++++- 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/cross-chain-provider.ts b/packages/sdk/src/cross-chain-provider.ts index 455f1ac1b..8acc1ecf5 100644 --- a/packages/sdk/src/cross-chain-provider.ts +++ b/packages/sdk/src/cross-chain-provider.ts @@ -421,9 +421,27 @@ export class CrossChainProvider implements ICrossChainProvider { } public async estimateL2MessageGasLimit( - message: MessageLike + message: MessageLike, + opts?: { + bufferPercent?: number + } ): Promise<BigNumber> { - throw new Error('Not implemented') + const resolved = await this.toCrossChainMessage(message) + + // L2 message gas estimation is only used for L1 => L2 messages. + if (resolved.direction === MessageDirection.L2_TO_L1) { + throw new Error(`cannot estimate gas limit for L2 => L1 message`) + } + + const estimate = await this.l2Provider.estimateGas({ + from: resolved.sender, + to: resolved.target, + data: resolved.message, + }) + + // Return the estimate plus a buffer of 20% just in case. + const bufferPercent = opts?.bufferPercent || 20 + return estimate.mul(100 + bufferPercent).div(100) } public async estimateMessageWaitTimeSeconds( diff --git a/packages/sdk/src/interfaces/cross-chain-provider.ts b/packages/sdk/src/interfaces/cross-chain-provider.ts index fb75a751e..5fe4541f7 100644 --- a/packages/sdk/src/interfaces/cross-chain-provider.ts +++ b/packages/sdk/src/interfaces/cross-chain-provider.ts @@ -201,9 +201,16 @@ export interface ICrossChainProvider { * L1 => L2 messages. You would supply this gas limit when sending the message to L2. * * @param message Message get a gas estimate for. + * @param opts Options object. + * @param opts.bufferPercent Percentage of gas to add to the estimate. Defaults to 20. * @returns Estimates L2 gas limit. */ - estimateL2MessageGasLimit(message: MessageLike): Promise<BigNumber> + estimateL2MessageGasLimit( + message: MessageLike, + opts?: { + bufferPercent?: number + } + ): Promise<BigNumber> /** * Returns the estimated amount of time before the message can be executed. When this is a diff --git a/packages/sdk/test/cross-chain-provider.spec.ts b/packages/sdk/test/cross-chain-provider.spec.ts index a4adc58cc..8d8fc34d1 100644 --- a/packages/sdk/test/cross-chain-provider.spec.ts +++ b/packages/sdk/test/cross-chain-provider.spec.ts @@ -1,4 +1,5 @@ import { Provider } from '@ethersproject/abstract-provider' +import { expectApprox } from '@eth-optimism/core-utils' import { Contract } from 'ethers' import { ethers } from 'hardhat' @@ -1091,7 +1092,93 @@ describe('CrossChainProvider', () => { }) describe('estimateL2MessageGasLimit', () => { - it('should perform a gas estimation of the L2 action') + let provider: CrossChainProvider + beforeEach(async () => { + provider = new CrossChainProvider({ + l1Provider: ethers.provider, + l2Provider: ethers.provider, + l1ChainId: 31337, + }) + }) + + describe('when the message is an L1 to L2 message', () => { + it('should return an accurate gas estimate plus a ~20% buffer', async () => { + const message = { + direction: MessageDirection.L1_TO_L2, + target: '0x' + '11'.repeat(20), + sender: '0x' + '22'.repeat(20), + message: '0x' + '33'.repeat(64), + messageNonce: 1234, + logIndex: 0, + blockNumber: 1234, + transactionHash: '0x' + '44'.repeat(32), + } + + const estimate = await ethers.provider.estimateGas({ + to: message.target, + from: message.sender, + data: message.message, + }) + + // Approximately 20% greater than the estimate, +/- 1%. + expectApprox( + await provider.estimateL2MessageGasLimit(message), + estimate.mul(120).div(100), + { + percentUpperDeviation: 1, + percentLowerDeviation: 1, + } + ) + }) + + it('should return an accurate gas estimate when a custom buffer is provided', async () => { + const message = { + direction: MessageDirection.L1_TO_L2, + target: '0x' + '11'.repeat(20), + sender: '0x' + '22'.repeat(20), + message: '0x' + '33'.repeat(64), + messageNonce: 1234, + logIndex: 0, + blockNumber: 1234, + transactionHash: '0x' + '44'.repeat(32), + } + + const estimate = await ethers.provider.estimateGas({ + to: message.target, + from: message.sender, + data: message.message, + }) + + // Approximately 30% greater than the estimate, +/- 1%. + expectApprox( + await provider.estimateL2MessageGasLimit(message, { + bufferPercent: 30, + }), + estimate.mul(130).div(100), + { + percentUpperDeviation: 1, + percentLowerDeviation: 1, + } + ) + }) + }) + + describe('when the message is an L2 to L1 message', () => { + it('should throw an error', async () => { + const message = { + direction: MessageDirection.L2_TO_L1, + target: '0x' + '11'.repeat(20), + sender: '0x' + '22'.repeat(20), + message: '0x' + '33'.repeat(64), + messageNonce: 1234, + logIndex: 0, + blockNumber: 1234, + transactionHash: '0x' + '44'.repeat(32), + } + + await expect(provider.estimateL2MessageGasLimit(message)).to.be.rejected + }) + }) }) describe('estimateMessageWaitTimeBlocks', () => { -- 2.23.0