From 2af8ca4cef62703705504e40fc50072d5cfa1257 Mon Sep 17 00:00:00 2001 From: rouzwelt Date: Mon, 20 Jan 2025 16:11:03 +0000 Subject: [PATCH] fix L1 gas estimation bug (#291) [skip ci] --- src/gas.ts | 8 ++---- src/modes/interOrderbook.ts | 42 ++++++++++++++++++++++++++++ src/modes/intraOrderbook.ts | 42 ++++++++++++++++++++++++++++ src/modes/routeProcessor.ts | 42 ++++++++++++++++++++++++++++ test/findOpp.test.js | 36 ++++++++++++++++++++++++ test/gas.test.ts | 8 ++---- test/mode-interOrderbook.test.js | 24 ++++++++++++++++ test/mode-intraOrderbook.test.js | 24 ++++++++++++++++ test/mode-routeProcessor.test.js | 48 ++++++++++++++++++++++++++++++++ test/processPair.test.js | 40 ++++++++++++++++++++++++++ 10 files changed, 304 insertions(+), 10 deletions(-) diff --git a/src/gas.ts b/src/gas.ts index 2de97609..aba0cd5e 100644 --- a/src/gas.ts +++ b/src/gas.ts @@ -25,7 +25,6 @@ export async function estimateGasCost( const result = { gas, gasPrice, - l1Gas: 0n, l1GasPrice: 0n, l1Cost: 0n, totalGasCost: gasPrice * gas, @@ -36,14 +35,13 @@ export async function estimateGasCost( if (typeof l1GasPrice !== "bigint") { l1GasPrice = (await l1Signer_.getL1BaseFee()) as bigint; } - const l1Gas = await l1Signer_.estimateL1Gas({ + const l1Cost = await l1Signer_.estimateL1Fee({ to: tx.to, data: tx.data, }); - result.l1Gas = l1Gas; result.l1GasPrice = l1GasPrice; - result.l1Cost = l1Gas * l1GasPrice; - result.totalGasCost += result.l1Cost; + result.l1Cost = l1Cost; + result.totalGasCost += l1Cost; } catch {} } return result; diff --git a/src/modes/interOrderbook.ts b/src/modes/interOrderbook.ts index 19dff052..1f47df7b 100644 --- a/src/modes/interOrderbook.ts +++ b/src/modes/interOrderbook.ts @@ -125,6 +125,23 @@ export async function dryrun({ const estimation = await estimateGasCost(rawtx, signer, config, l1GasPrice); l1Cost = estimation.l1Cost; gasLimit = ethers.BigNumber.from(estimation.gas).mul(config.gasLimitMultiplier).div(100); + + // include dryrun headroom gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.headroom", + ); } catch (e) { const isNodeError = containsNodeError(e as BaseError); const errMsg = errorSnapshot("", e); @@ -153,6 +170,10 @@ export async function dryrun({ // sender output which is already called above if (config.gasCoveragePercentage !== "0") { const headroom = (Number(config.gasCoveragePercentage) * 1.03).toFixed(); + spanAttributes["gasEst.headroom.minBountyExpected"] = gasCost + .mul(headroom) + .div("100") + .toString(); task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(inputToEthPrice), @@ -188,6 +209,23 @@ export async function dryrun({ } rawtx.gas = gasLimit.toBigInt(); gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); + + // include dryrun final gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.final", + ); task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(inputToEthPrice), @@ -203,6 +241,10 @@ export async function dryrun({ takeOrdersConfigStruct, task, ]); + spanAttributes["gasEst.final.minBountyExpected"] = gasCost + .mul(config.gasCoveragePercentage) + .div("100") + .toString(); } catch (e) { const isNodeError = containsNodeError(e as BaseError); const errMsg = errorSnapshot("", e); diff --git a/src/modes/intraOrderbook.ts b/src/modes/intraOrderbook.ts index afdf786f..db895532 100644 --- a/src/modes/intraOrderbook.ts +++ b/src/modes/intraOrderbook.ts @@ -117,6 +117,23 @@ export async function dryrun({ const estimation = await estimateGasCost(rawtx, signer, config, l1GasPrice); l1Cost = estimation.l1Cost; gasLimit = ethers.BigNumber.from(estimation.gas).mul(config.gasLimitMultiplier).div(100); + + // include dryrun headroom gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.headroom", + ); } catch (e) { // reason, code, method, transaction, error, stack, message const isNodeError = containsNodeError(e as BaseError); @@ -146,6 +163,10 @@ export async function dryrun({ // sender output which is already called above if (config.gasCoveragePercentage !== "0") { const headroom = (Number(config.gasCoveragePercentage) * 1.03).toFixed(); + spanAttributes["gasEst.headroom.minBountyExpected"] = gasCost + .mul(headroom) + .div("100") + .toString(); task.evaluable.bytecode = await parseRainlang( await getWithdrawEnsureRainlang( signer.account.address, @@ -190,6 +211,23 @@ export async function dryrun({ } rawtx.gas = gasLimit.toBigInt(); gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); + + // include dryrun final gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.final", + ); task.evaluable.bytecode = await parseRainlang( await getWithdrawEnsureRainlang( signer.account.address, @@ -214,6 +252,10 @@ export async function dryrun({ rawtx.data = obInterface.encodeFunctionData("multicall", [ [clear2Calldata, withdrawInputCalldata, withdrawOutputCalldata], ]); + spanAttributes["gasEst.final.minBountyExpected"] = gasCost + .mul(config.gasCoveragePercentage) + .div("100") + .toString(); } catch (e) { const isNodeError = containsNodeError(e as BaseError); const errMsg = errorSnapshot("", e); diff --git a/src/modes/routeProcessor.ts b/src/modes/routeProcessor.ts index 6788161a..45747ff7 100644 --- a/src/modes/routeProcessor.ts +++ b/src/modes/routeProcessor.ts @@ -189,6 +189,23 @@ export async function dryrun({ gasLimit = ethers.BigNumber.from(estimation.gas) .mul(config.gasLimitMultiplier) .div(100); + + // include dryrun headroom gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.headroom", + ); } catch (e) { // reason, code, method, transaction, error, stack, message const isNodeError = containsNodeError(e as BaseError); @@ -219,6 +236,10 @@ export async function dryrun({ // sender output which is already called above if (config.gasCoveragePercentage !== "0") { const headroom = (Number(config.gasCoveragePercentage) * 1.03).toFixed(); + spanAttributes["gasEst.headroom.minBountyExpected"] = gasCost + .mul(headroom) + .div("100") + .toString(); task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(ethPrice), @@ -254,6 +275,23 @@ export async function dryrun({ } rawtx.gas = gasLimit.toBigInt(); gasCost = gasLimit.mul(gasPrice).add(estimation.l1Cost); + + // include dryrun final gas estimation in otel logs + extendSpanAttributes( + spanAttributes, + { + gasLimit: estimation.gas.toString(), + totalCost: estimation.totalGasCost.toString(), + gasPrice: estimation.gasPrice.toString(), + ...(config.isSpecialL2 + ? { + l1Cost: estimation.l1Cost.toString(), + l1GasPrice: estimation.l1GasPrice.toString(), + } + : {}), + }, + "gasEst.final", + ); task.evaluable.bytecode = await parseRainlang( await getBountyEnsureRainlang( ethers.utils.parseUnits(ethPrice), @@ -269,6 +307,10 @@ export async function dryrun({ takeOrdersConfigStruct, task, ]); + spanAttributes["gasEst.final.minBountyExpected"] = gasCost + .mul(config.gasCoveragePercentage) + .div("100") + .toString(); } catch (e) { const isNodeError = containsNodeError(e as BaseError); const errMsg = errorSnapshot("", e); diff --git a/test/findOpp.test.js b/test/findOpp.test.js index b1b82feb..1d71152f 100644 --- a/test/findOpp.test.js +++ b/test/findOpp.test.js @@ -141,6 +141,18 @@ describe("Test find opp", async function () { marketPrice: formatUnits(getCurrentPrice(vaultBalance)), route: expectedRouteVisual, clearModePick: "rp4", + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -237,6 +249,18 @@ describe("Test find opp", async function () { foundOpp: true, maxInput: vaultBalance.toString(), clearModePick: "inter", + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -338,6 +362,18 @@ describe("Test find opp", async function () { oppBlockNumber, foundOpp: true, clearModePick: "intra", + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); diff --git a/test/gas.test.ts b/test/gas.test.ts index f8f1d85e..3d36a686 100644 --- a/test/gas.test.ts +++ b/test/gas.test.ts @@ -14,7 +14,7 @@ describe("Test gas", async function () { // mock L1 signer for L2 client const l1Signer = { getL1BaseFee: async () => 20n, - estimateL1Gas: async () => 5n, + estimateL1Fee: async () => 5n, }; // mock normal L1 signer const signer = { @@ -32,10 +32,9 @@ describe("Test gas", async function () { const expected1 = { gas: 55n, gasPrice: 2n, - l1Gas: 5n, l1GasPrice: 20n, - l1Cost: 20n * 5n, - totalGasCost: 2n * 55n + 20n * 5n, + l1Cost: 5n, + totalGasCost: 2n * 55n + 5n, }; assert.deepEqual(result1, expected1); @@ -45,7 +44,6 @@ describe("Test gas", async function () { const expected2 = { gas: 55n, gasPrice: 2n, - l1Gas: 0n, l1GasPrice: 0n, l1Cost: 0n, totalGasCost: 2n * 55n, diff --git a/test/mode-interOrderbook.test.js b/test/mode-interOrderbook.test.js index 807ce906..f5b3a198 100644 --- a/test/mode-interOrderbook.test.js +++ b/test/mode-interOrderbook.test.js @@ -124,6 +124,18 @@ describe("Test inter-orderbook dryrun", async function () { oppBlockNumber, foundOpp: true, maxInput: vaultBalance.toString(), + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -263,6 +275,18 @@ describe("Test inter-orderbook find opp", async function () { oppBlockNumber, foundOpp: true, maxInput: vaultBalance.toString(), + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); diff --git a/test/mode-intraOrderbook.test.js b/test/mode-intraOrderbook.test.js index b2a8c9c9..fd713563 100644 --- a/test/mode-intraOrderbook.test.js +++ b/test/mode-intraOrderbook.test.js @@ -127,6 +127,18 @@ describe("Test intra-orderbook dryrun", async function () { spanAttributes: { oppBlockNumber, foundOpp: true, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -263,6 +275,18 @@ describe("Test intra-orderbook find opp", async function () { spanAttributes: { oppBlockNumber, foundOpp: true, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); diff --git a/test/mode-routeProcessor.test.js b/test/mode-routeProcessor.test.js index 5602ff92..0c983006 100644 --- a/test/mode-routeProcessor.test.js +++ b/test/mode-routeProcessor.test.js @@ -130,6 +130,18 @@ describe("Test route processor dryrun", async function () { amountOut: formatUnits(getAmountOut(vaultBalance), 6), marketPrice: formatUnits(getCurrentPrice(vaultBalance)), route: expectedRouteVisual, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -338,6 +350,18 @@ describe("Test route processor find opp", async function () { amountOut: formatUnits(getAmountOut(vaultBalance), 6), marketPrice: formatUnits(getCurrentPrice(vaultBalance)), route: expectedRouteVisual, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -427,6 +451,18 @@ describe("Test route processor find opp", async function () { getCurrentPrice(ethers.utils.parseUnits("9999999.701976776123046875")), ), route: expectedRouteVisual, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); @@ -640,6 +676,18 @@ describe("Test find opp with retries", async function () { amountOut: formatUnits(getAmountOut(vaultBalance), 6), marketPrice: formatUnits(getCurrentPrice(vaultBalance)), route: expectedRouteVisual, + "gasEst.final.gasLimit": gasLimitEstimation.toString(), + "gasEst.final.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.final.gasPrice": gasPrice.toString(), + "gasEst.final.minBountyExpected": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "gasEst.headroom.totalCost": gasLimitEstimation.mul(gasPrice).toString(), + "gasEst.headroom.gasPrice": gasPrice.toString(), + "gasEst.headroom.minBountyExpected": gasLimitEstimation + .mul(gasPrice) + .mul(103) + .div(100) + .toString(), }, }; assert.deepEqual(result, expected); diff --git a/test/processPair.test.js b/test/processPair.test.js index 8bcb2999..f67eaf40 100644 --- a/test/processPair.test.js +++ b/test/processPair.test.js @@ -173,6 +173,14 @@ describe("Test process pair", async function () { vaultBalance, ), ), + "details.gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "details.gasEst.headroom.gasPrice": gasPrice + .mul(config.gasPriceMultiplier) + .div(100) + .toString(), + "details.gasEst.headroom.totalCost": gasLimitEstimation + .mul(gasPrice.mul(config.gasPriceMultiplier).div(100)) + .toString(), }, }; assert.deepEqual(result, expected); @@ -254,6 +262,14 @@ describe("Test process pair", async function () { vaultBalance, ), ), + "details.gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "details.gasEst.headroom.gasPrice": gasPrice + .mul(config.gasPriceMultiplier) + .div(100) + .toString(), + "details.gasEst.headroom.totalCost": gasLimitEstimation + .mul(gasPrice.mul(config.gasPriceMultiplier).div(100)) + .toString(), }, }; assert.deepEqual(result, expected); @@ -541,6 +557,14 @@ describe("Test process pair", async function () { vaultBalance, ), ), + "details.gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "details.gasEst.headroom.gasPrice": gasPrice + .mul(config.gasPriceMultiplier) + .div(100) + .toString(), + "details.gasEst.headroom.totalCost": gasLimitEstimation + .mul(gasPrice.mul(config.gasPriceMultiplier).div(100)) + .toString(), }, }; assert.deepEqual(error, expected); @@ -632,6 +656,14 @@ describe("Test process pair", async function () { vaultBalance, ), ), + "details.gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "details.gasEst.headroom.gasPrice": gasPrice + .mul(config.gasPriceMultiplier) + .div(100) + .toString(), + "details.gasEst.headroom.totalCost": gasLimitEstimation + .mul(gasPrice.mul(config.gasPriceMultiplier).div(100)) + .toString(), }, }; assert.deepEqual(error, expected); @@ -738,6 +770,14 @@ describe("Test process pair", async function () { vaultBalance, ), ), + "details.gasEst.headroom.gasLimit": gasLimitEstimation.toString(), + "details.gasEst.headroom.gasPrice": gasPrice + .mul(config.gasPriceMultiplier) + .div(100) + .toString(), + "details.gasEst.headroom.totalCost": gasLimitEstimation + .mul(gasPrice.mul(config.gasPriceMultiplier).div(100)) + .toString(), }, }; assert.deepEqual(error, expected);