Skip to content

Commit 888dea6

Browse files
feat(evm): compute intrinsic gas limit for send on evm
1 parent f48e19f commit 888dea6

File tree

7 files changed

+88
-10
lines changed

7 files changed

+88
-10
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ledgerhq/coin-evm": minor
3+
---
4+
5+
compute intrinsic gas limit for send on evm

libs/coin-modules/coin-evm/src/logic/common.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,12 @@ export function getErc20Data(recipient: string, amount: bigint): Buffer {
9696
return Buffer.from(data.slice(2), "hex");
9797
}
9898

99-
function getCallData(intent: TransactionIntent<MemoNotSupported, BufferTxData>): Buffer {
99+
export function getCallData(intent: TransactionIntent<MemoNotSupported, BufferTxData>): Buffer {
100100
const data = intent.data?.value;
101101
if (Buffer.isBuffer(data) && data.length) return data;
102-
return isNative(intent.asset) ? Buffer.from([]) : getErc20Data(intent.recipient, intent.amount);
102+
return isNative(intent.asset) || !intent.recipient
103+
? Buffer.from([])
104+
: getErc20Data(intent.recipient, intent.amount);
103105
}
104106

105107
export async function prepareUnsignedTxParams(
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { DEFAULT_GAS_LIMIT } from "../utils";
2+
import { computeIntrinsicGasLimit } from "./computeGasLimit";
3+
4+
describe("computeGasLimit", () => {
5+
it.each([
6+
{
7+
callData: "",
8+
expectedGasLimit: 21000n,
9+
},
10+
{
11+
callData:
12+
"a9059cbb00000000000000000000000066c4371ae8ffed2ec1c2ebbbccfb7e494181e1e30000000000000000000000000000000000000000000000000000000000000000",
13+
expectedGasLimit: 21560n,
14+
},
15+
{
16+
callData:
17+
"a9059cbb000000000000000000000000d8ff72a08408b97655ee94381b8fa24ba7d6f5ac0000000000000000000000000000000000000000000000000000000000895440",
18+
expectedGasLimit: 21596n,
19+
},
20+
{
21+
callData:
22+
"a9059cbb00000000000000000000000066c4371ae8ffed2ec1c2ebbbccfb7e494181e1e30000000000000000000000000000000000000000000000000000000000b71b00",
23+
expectedGasLimit: 21584n,
24+
},
25+
])("should compute intrinsic gas", ({ callData, expectedGasLimit }) => {
26+
const intrinsicGasLimit = computeIntrinsicGasLimit(
27+
BigInt(DEFAULT_GAS_LIMIT.toFixed(0)),
28+
Buffer.from(callData, "hex"),
29+
);
30+
expect(intrinsicGasLimit).toEqual(expectedGasLimit);
31+
});
32+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function computeIntrinsicGasLimit(defaultGasLimit: bigint, callData: Buffer): bigint {
2+
let intrinsicGasLimit = defaultGasLimit;
3+
for (let index = 0; index < callData.length; index++) {
4+
intrinsicGasLimit += callData[index] === 0 ? 4n : 16n;
5+
}
6+
7+
return intrinsicGasLimit;
8+
}

libs/coin-modules/coin-evm/src/logic/validateIntent.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ import { EvmCoinConfig, setCoinConfig } from "../config";
2525
import ledgerExplorer from "../network/explorer/ledger";
2626
import ledgerGasTracker from "../network/gasTracker/ledger";
2727
import ledgerNode from "../network/node/ledger";
28+
import * as computeGasLimitModule from "./computeGasLimit";
2829
import { validateIntent } from "./validateIntent";
2930

31+
jest.mock("./computeGasLimit", () => ({
32+
...jest.requireActual("./computeGasLimit"),
33+
computeIntrinsicGasLimit: jest.fn(),
34+
}));
35+
3036
function legacyIntent(
3137
intent: Omit<Partial<TransactionIntent>, "type">,
3238
): TransactionIntent<MemoNotSupported, BufferTxData> {
@@ -106,7 +112,7 @@ describe("validateIntent", () => {
106112
jest.spyOn(ledgerNode, "getTransactionCount").mockResolvedValue(30);
107113
});
108114
afterEach(() => {
109-
jest.restoreAllMocks();
115+
jest.clearAllMocks();
110116
});
111117

112118
describe("fee ratio", () => {
@@ -122,7 +128,10 @@ describe("validateIntent", () => {
122128
])("%s with too high fees on a %s asset", async (_s, assetType, expectedWarnings) => {
123129
const res = await validateIntent(
124130
{} as CryptoCurrency,
125-
eip1559Intent({ amount: 1n, asset: { type: assetType } }),
131+
eip1559Intent({
132+
amount: 1n,
133+
asset: { type: assetType },
134+
}),
126135
[{ value: 50n, asset: { type: "native" } }],
127136
{
128137
value: 2n,
@@ -479,13 +488,18 @@ describe("validateIntent", () => {
479488
});
480489

481490
it("detects gas limit being too low in a tx with an error", async () => {
491+
const intrinsicGasLimit = 21000n;
492+
(computeGasLimitModule.computeIntrinsicGasLimit as jest.Mock).mockReturnValue(
493+
intrinsicGasLimit,
494+
);
495+
482496
const res = await validateIntent(
483497
{} as CryptoCurrency,
484498
createIntent({}),
485499
[{ value: 50n, asset: { type: "native" } }],
486500
{
487501
value: 0n,
488-
parameters: { gasLimit: 20000n }, // min should be 21000
502+
parameters: { gasLimit: intrinsicGasLimit - 1n }, // min should be 21000
489503
},
490504
);
491505

@@ -494,16 +508,23 @@ describe("validateIntent", () => {
494508
gasLimit: new GasLessThanEstimate(),
495509
}),
496510
);
511+
512+
expect(computeGasLimitModule.computeIntrinsicGasLimit).toHaveBeenCalledTimes(1);
497513
});
498514

499515
it("detects custom gas limit being too low in a tx with an error", async () => {
516+
const intrinsicGasLimit = 21000n;
517+
(computeGasLimitModule.computeIntrinsicGasLimit as jest.Mock).mockReturnValue(
518+
intrinsicGasLimit,
519+
);
520+
500521
const res = await validateIntent(
501522
{} as CryptoCurrency,
502523
createIntent({}),
503524
[{ value: 50n, asset: { type: "native" } }],
504525
{
505526
value: 0n,
506-
parameters: { customGasLimit: 20000n }, // min should be 21000
527+
parameters: { customGasLimit: intrinsicGasLimit - 1n },
507528
},
508529
);
509530

@@ -512,6 +533,8 @@ describe("validateIntent", () => {
512533
gasLimit: new GasLessThanEstimate(),
513534
}),
514535
);
536+
537+
expect(computeGasLimitModule.computeIntrinsicGasLimit).toHaveBeenCalledTimes(1);
515538
});
516539

517540
it("detects customGasLimit being lower than gasLimit with a warning", async () => {

libs/coin-modules/coin-evm/src/logic/validateIntent.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@ import { getGasTracker } from "../network/gasTracker";
3030
import { isNative, TransactionTypes } from "../types";
3131
import { DEFAULT_GAS_LIMIT, isEthAddress } from "../utils";
3232
import {
33+
getCallData,
3334
getTransactionType,
3435
isApiGasOptions,
3536
isEip1559FeeEstimation,
3637
isEip55Address,
3738
isLegacyFeeEstimation,
3839
} from "./common";
40+
import { computeIntrinsicGasLimit } from "./computeGasLimit";
3941
import estimateFees from "./estimateFees";
4042

4143
function assetsAreEqual(asset1: AssetInfo, asset2: AssetInfo): boolean {
@@ -124,7 +126,7 @@ function validateRecipient(
124126
*/
125127
async function validateGas(
126128
currency: CryptoCurrency,
127-
intent: TransactionIntent,
129+
intent: TransactionIntent<MemoNotSupported, BufferTxData>,
128130
balances: Balance[],
129131
estimatedFees: FeeEstimation,
130132
): Promise<Pick<TransactionValidation, "errors" | "warnings">> {
@@ -139,17 +141,23 @@ async function validateGas(
139141
typeof estimatedFees.parameters?.customGasLimit === "bigint" &&
140142
estimatedFees.parameters.customGasLimit;
141143

144+
const callData = getCallData(intent);
145+
const intrinsicGasLimit = computeIntrinsicGasLimit(
146+
BigInt(DEFAULT_GAS_LIMIT.toFixed(0)),
147+
callData,
148+
);
149+
142150
// Gas Limit
143151
if (typeof customGasLimit === "bigint") {
144152
if (customGasLimit === 0n) {
145153
errors.gasLimit = new FeeNotLoaded();
146-
} else if (customGasLimit < BigInt(DEFAULT_GAS_LIMIT.toFixed(0))) {
154+
} else if (customGasLimit < intrinsicGasLimit) {
147155
errors.gasLimit = new GasLessThanEstimate();
148156
}
149157
} else {
150158
if (gasLimit === 0n) {
151159
errors.gasLimit = new FeeNotLoaded();
152-
} else if (gasLimit < BigInt(DEFAULT_GAS_LIMIT.toFixed(0))) {
160+
} else if (gasLimit < intrinsicGasLimit) {
153161
errors.gasLimit = new GasLessThanEstimate();
154162
}
155163
}

libs/coin-modules/coin-evm/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@
1919
"include": [
2020
"src/**/*"
2121
]
22-
}
22+
}

0 commit comments

Comments
 (0)