Skip to content

Commit d79a2e3

Browse files
evm: fix CALL(CODE) gas (#3195)
1 parent 2b5f86a commit d79a2e3

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

packages/evm/src/opcodes/functions.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ export const handlers: Map<number, OpHandler> = new Map([
10251025
// 0xf1: CALL
10261026
[
10271027
0xf1,
1028-
async function (runState: RunState) {
1028+
async function (runState: RunState, common: Common) {
10291029
const [_currentGasLimit, toAddr, value, inOffset, inLength, outOffset, outLength] =
10301030
runState.stack.popN(7)
10311031
const toAddress = new Address(addresstoBytes(toAddr))
@@ -1035,7 +1035,13 @@ export const handlers: Map<number, OpHandler> = new Map([
10351035
data = runState.memory.read(Number(inOffset), Number(inLength), true)
10361036
}
10371037

1038-
const gasLimit = runState.messageGasLimit!
1038+
let gasLimit = runState.messageGasLimit!
1039+
if (value !== BIGINT_0) {
1040+
const callStipend = common.param('gasPrices', 'callStipend')
1041+
runState.interpreter.addStipend(callStipend)
1042+
gasLimit += callStipend
1043+
}
1044+
10391045
runState.messageGasLimit = undefined
10401046

10411047
const ret = await runState.interpreter.call(gasLimit, toAddress, value, data)
@@ -1047,12 +1053,18 @@ export const handlers: Map<number, OpHandler> = new Map([
10471053
// 0xf2: CALLCODE
10481054
[
10491055
0xf2,
1050-
async function (runState: RunState) {
1056+
async function (runState: RunState, common: Common) {
10511057
const [_currentGasLimit, toAddr, value, inOffset, inLength, outOffset, outLength] =
10521058
runState.stack.popN(7)
10531059
const toAddress = new Address(addresstoBytes(toAddr))
10541060

1055-
const gasLimit = runState.messageGasLimit!
1061+
let gasLimit = runState.messageGasLimit!
1062+
if (value !== BIGINT_0) {
1063+
const callStipend = common.param('gasPrices', 'callStipend')
1064+
runState.interpreter.addStipend(callStipend)
1065+
gasLimit += callStipend
1066+
}
1067+
10561068
runState.messageGasLimit = undefined
10571069

10581070
let data = new Uint8Array(0)

packages/evm/src/opcodes/gas.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
372372
gas += common.param('gasPrices', 'callNewAccount')
373373
}
374374

375-
let gasLimit = maxCallGas(
375+
const gasLimit = maxCallGas(
376376
currentGasLimit,
377377
runState.interpreter.getGasLeft() - gas,
378378
runState,
@@ -388,12 +388,6 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
388388
trap(ERROR.OUT_OF_GAS)
389389
}
390390

391-
if (value !== BIGINT_0) {
392-
const callStipend = common.param('gasPrices', 'callStipend')
393-
runState.interpreter.addStipend(callStipend)
394-
gasLimit += callStipend
395-
}
396-
397391
runState.messageGasLimit = gasLimit
398392
return gas
399393
},
@@ -415,7 +409,7 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
415409
if (value !== BIGINT_0) {
416410
gas += common.param('gasPrices', 'callValueTransfer')
417411
}
418-
let gasLimit = maxCallGas(
412+
const gasLimit = maxCallGas(
419413
currentGasLimit,
420414
runState.interpreter.getGasLeft() - gas,
421415
runState,
@@ -426,11 +420,6 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
426420
if (gasLimit > runState.interpreter.getGasLeft() - gas) {
427421
trap(ERROR.OUT_OF_GAS)
428422
}
429-
if (value !== BIGINT_0) {
430-
const callStipend = common.param('gasPrices', 'callStipend')
431-
runState.interpreter.addStipend(callStipend)
432-
gasLimit += callStipend
433-
}
434423

435424
runState.messageGasLimit = gasLimit
436425
return gas

packages/evm/test/runCall.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Chain, Common, Hardfork } from '@ethereumjs/common'
22
import {
33
Account,
44
Address,
5+
BIGINT_100,
56
MAX_UINT64,
67
bytesToBigInt,
78
bytesToHex,
@@ -716,4 +717,45 @@ describe('RunCall tests', () => {
716717
const res2 = await evm.runCall(runCallArgs2)
717718
assert.ok(res2.execResult.exceptionError?.error === ERROR.OUT_OF_GAS)
718719
})
720+
721+
it('ensure call and callcode handle gas stipend correctly', async () => {
722+
// See: https://github.com/ethereumjs/ethereumjs-monorepo/issues/3194
723+
const common = new Common({ chain: Chain.Mainnet, hardfork: Hardfork.Shanghai })
724+
const evm = new EVM({
725+
common,
726+
})
727+
for (const opcode of ['f1', 'f2']) {
728+
// Code to either CALL or CALLCODE into the own address, with value 1
729+
// JUMPDEST added at the start to track how much CALL(CODE)s are made
730+
const contractCode = hexToBytes(`0x5B6000808080600130611a90${opcode}`)
731+
const contractAddress = Address.fromString('0x000000000000000000000000636F6E7472616374')
732+
await evm.stateManager.putContractCode(contractAddress, contractCode)
733+
734+
const account = await evm.stateManager.getAccount(contractAddress)
735+
account!.balance = BIGINT_100
736+
737+
await evm.stateManager.putAccount(contractAddress, account)
738+
739+
const runCallArgs = {
740+
gasLimit: BigInt(50000 - 21000),
741+
to: contractAddress,
742+
}
743+
744+
let jumpdestCalls = 0
745+
746+
evm.events.on('step', (e) => {
747+
if (e.opcode.name === 'JUMPDEST') {
748+
jumpdestCalls++
749+
}
750+
})
751+
752+
await evm.runCall(runCallArgs)
753+
// JUMPDEST should be called twice:
754+
// 1: call into the contract (externally, via tx)
755+
// 2: call(code) into the contract (internally)
756+
// If jumpdest would run >=3 times, it means the (2) has enough gas to pay for CALL(CODE)
757+
// This is not correct
758+
assert.ok(jumpdestCalls === 2, 'called JUMPDEST twice')
759+
}
760+
})
719761
})

0 commit comments

Comments
 (0)