Skip to content

Commit 135dceb

Browse files
committed
Implement storex gas function and base evmmax cas cost model
1 parent 00e2930 commit 135dceb

File tree

2 files changed

+104
-16
lines changed

2 files changed

+104
-16
lines changed

packages/evm/src/opcodes/gas.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import {
77
BIGINT_32,
88
BIGINT_64,
99
bigIntToBytes,
10+
bytesToBigInt,
1011
equalsBytes,
12+
hexToBigInt,
1113
setLengthLeft,
1214
} from '@ethereumjs/util'
1315

@@ -21,6 +23,8 @@ import { accessAddressEIP2929, accessStorageEIP2929 } from './EIP2929.ts'
2123
import {
2224
createAddressFromStackBigInt,
2325
divCeil,
26+
evmmaxMemoryGasCost,
27+
isPowerOfTwo,
2428
maxCallGas,
2529
setLengthLeftStorage,
2630
subMemUsage,
@@ -30,6 +34,11 @@ import {
3034

3135
import type { Common } from '@ethereumjs/common'
3236
import type { Address } from '@ethereumjs/util'
37+
import {
38+
MAX_ALLOC_SIZE,
39+
SETMODX_ODD_MODULUS_COST,
40+
setmodxOddModulusCost,
41+
} from '../evmmax/constants.js'
3342
import type { RunState } from '../interpreter.ts'
3443

3544
const EXTCALL_TARGET_MAX = BigInt(2) ** BigInt(8 * 20) - BigInt(1)
@@ -47,6 +56,11 @@ async function eip7702GasCost(
4756
return BIGINT_0
4857
}
4958

59+
const MAX_UINT64 = 2n ** 64n - 1n
60+
function isUint64(value: bigint): boolean {
61+
return value >= 0n && value <= MAX_UINT64
62+
}
63+
5064
/**
5165
* This file returns the dynamic parts of opcodes which have dynamic gas
5266
* These are not pure functions: some edit the size of the memory
@@ -772,7 +786,37 @@ export const dynamicGasHandlers: Map<number, AsyncDynamicGasHandler | SyncDynami
772786
/* SETMODX */
773787
0xc0,
774788
async function (runState, gas, common): Promise<bigint> {
775-
return 0n
789+
const [modId, modOffset, modSize, allocCount] = runState.stack.peek(4)
790+
791+
if (!isUint64(modId) || !isUint64(modSize) || !isUint64(allocCount)) {
792+
trap('one or more parameters overflows 64 bits')
793+
}
794+
if (runState.evmmaxState.getAlloced().get(Number(modId)) !== undefined) {
795+
return 0n
796+
}
797+
if (modSize > 96n) {
798+
trap('modulus cannot exceed 768 bits in width')
799+
}
800+
if (!isUint64(modOffset + modSize)) {
801+
trap('modulus offset + size overflows uint64')
802+
}
803+
if (allocCount > 256) {
804+
trap('cannot allocate more than 256 field elements per modulus id')
805+
}
806+
const paddedModSize = (modSize + 7n) / 8n
807+
const precompCost = SETMODX_ODD_MODULUS_COST[Number(paddedModSize)]
808+
809+
const allocSize = paddedModSize * allocCount
810+
if (runState.evmmaxState.allocSize() + allocSize > MAX_ALLOC_SIZE) {
811+
trap('call context evmmax allocation threshold exceeded')
812+
}
813+
814+
const memCost = evmmaxMemoryGasCost(runState, common, allocSize, 0n, 0n) // TODO should I be setting length and offset to 0?
815+
const modBytes = runState.memory.read(Number(modOffset), Number(modSize))
816+
if (!isPowerOfTwo(bytesToBigInt(modBytes))) {
817+
return BigInt(precompCost) + memCost
818+
}
819+
return memCost
776820
},
777821
],
778822
[

packages/evm/src/opcodes/util.ts

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
bytesToHex,
1111
createAddressFromBigInt,
1212
equalsBytes,
13+
hexToBigInt,
1314
setLengthLeft,
1415
setLengthRight,
1516
} from '@ethereumjs/util'
@@ -197,31 +198,74 @@ export function maxCallGas(
197198
}
198199
}
199200

200-
/**
201-
* Subtracts the amount needed for memory usage from `runState.gasLeft`
202-
*/
203-
export function subMemUsage(runState: RunState, offset: bigint, length: bigint, common: Common) {
201+
export function isPowerOfTwo(val: bigint): boolean {
202+
if (val <= 0n) return false
203+
204+
const bin = val.toString(2)
205+
const topBitIndex = bin.length - 1
206+
const cleared = val - (1n << BigInt(topBitIndex))
207+
208+
return cleared === 0n
209+
}
210+
211+
export function evmmaxMemoryGasCost(
212+
runState: RunState,
213+
common: Common,
214+
newEVMMAXMemSize: bigint,
215+
offset: bigint,
216+
length: bigint,
217+
) {
218+
if (runState.memoryWordCount === 0n && newEVMMAXMemSize === 0n) {
219+
return 0n
220+
}
221+
204222
// YP (225): access with zero length will not extend the memory
205223
if (length === BIGINT_0) return BIGINT_0
206224

207225
const newMemoryWordCount = divCeil(offset + length, BIGINT_32)
208226
if (newMemoryWordCount <= runState.memoryWordCount) return BIGINT_0
209227

210-
const words = newMemoryWordCount
211-
const fee = common.param('memoryGas')
212-
const quadCoefficient = common.param('quadCoefficientDivGas')
213-
// words * 3 + words ^2 / 512
214-
let cost = words * fee + (words * words) / quadCoefficient
228+
let newMemSize = newMemoryWordCount
215229

216-
if (cost > runState.highestMemCost) {
217-
const currentHighestMemCost = runState.highestMemCost
218-
runState.highestMemCost = cost
219-
cost -= currentHighestMemCost
230+
if (newMemSize > hexToBigInt('0x1FFFFFFFE0')) {
231+
trap('gas uint64 overflow') // TODO is there an error code for gas overflow?
220232
}
233+
const newMemSizePadded = newMemSize * 32n
221234

222-
runState.memoryWordCount = newMemoryWordCount
235+
const curEVMMAXMemSizePadded = runState.evmmaxState.getActive().getAllocatedSize() * 32
236+
const newEVMMAXMemSizePadded = Number(newEVMMAXMemSize) * 32
223237

224-
return cost
238+
if (
239+
newMemSizePadded > BigInt(runState.memory._store.length) ||
240+
newEVMMAXMemSizePadded > curEVMMAXMemSizePadded
241+
) {
242+
if (newMemSize <= BigInt(runState.memory._store.length)) {
243+
newMemSize = BigInt(runState.memory._store.length)
244+
}
245+
const newEffectiveMemSizeWords = newEVMMAXMemSize + newMemSize // toWordSize?
246+
const words = newEffectiveMemSizeWords
247+
const fee = common.param('memoryGas')
248+
const quadCoefficient = common.param('quadCoefficientDivGas')
249+
// words * 3 + words ^2 / 512
250+
let cost = words * fee + (words * words) / quadCoefficient
251+
if (cost > runState.highestMemCost) {
252+
const currentHighestMemCost = runState.highestMemCost
253+
runState.highestMemCost = cost
254+
cost -= currentHighestMemCost
255+
}
256+
runState.memoryWordCount = newMemoryWordCount
257+
258+
return cost
259+
}
260+
261+
return 0n
262+
}
263+
264+
/**
265+
* Subtracts the amount needed for memory usage from `runState.gasLeft`
266+
*/
267+
export function subMemUsage(runState: RunState, offset: bigint, length: bigint, common: Common) {
268+
return evmmaxMemoryGasCost(runState, common, runState.evmmaxState.allocSize(), offset, length)
225269
}
226270

227271
/**

0 commit comments

Comments
 (0)