Skip to content

Commit 3064b8e

Browse files
vm/common: add EIP 2565, ModExp precompile gas cost
1 parent 582b4ef commit 3064b8e

File tree

6 files changed

+233
-24
lines changed

6 files changed

+233
-24
lines changed

packages/common/src/eips/2565.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "EIP-2565",
3+
"number": 2565,
4+
"comment": "ModExp gas cost",
5+
"url": "https://eips.ethereum.org/EIPS/eip-2565",
6+
"status": "Last call",
7+
"minimumHardfork": "byzantium",
8+
"gasConfig": {},
9+
"gasPrices": {
10+
"modexpGquaddivisor": {
11+
"v": 3,
12+
"d": "Gquaddivisor from modexp precompile for gas calculation"
13+
}
14+
},
15+
"vm": {},
16+
"pow": {}
17+
}

packages/common/src/eips/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { eipsType } from './../types'
33
export const EIPs: eipsType = {
44
2315: require('./2315.json'),
55
2537: require('./2537.json'),
6+
2565: require('./2565.json'),
67
2929: require('./2929.json'),
78
}

packages/vm/lib/evm/precompiles/05-modexp.ts

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,38 @@ function multComplexity(x: BN): BN {
2222
}
2323
}
2424

25+
function calculateGasEIP2565(
26+
baseLength: BN,
27+
modulusLength: BN,
28+
exponentLength: BN,
29+
exponent: BN
30+
): BN {
31+
console.log(
32+
baseLength.toNumber(),
33+
modulusLength.toNumber(),
34+
exponentLength.toNumber(),
35+
exponent.toNumber()
36+
)
37+
const maxLength = BN.max(baseLength, modulusLength)
38+
const words = maxLength.divn(8)
39+
// Ceil operation
40+
if (maxLength.mod(new BN(8)).gtn(0)) {
41+
words.iaddn(1)
42+
}
43+
const multiplicationComplexity = words.pow(new BN(2))
44+
let iterationCount
45+
if (exponentLength.lten(32) && exponent.eqn(0)) {
46+
iterationCount = new BN(0)
47+
} else if (exponentLength.lten(32)) {
48+
iterationCount = new BN(exponent.bitLength() - 1)
49+
} else {
50+
const expMask = exponent.and(new BN(Buffer.alloc(32, 0xff)))
51+
iterationCount = new BN(8).mul(exponentLength.subn(32)).addn(expMask.bitLength() - 1)
52+
}
53+
iterationCount = BN.max(new BN(1), iterationCount)
54+
return BN.max(new BN(200), multiplicationComplexity.mul(iterationCount).divn(3))
55+
}
56+
2557
function getAdjustedExponentLength(data: Buffer): BN {
2658
let expBytesStart
2759
try {
@@ -86,7 +118,35 @@ export default function (opts: PrecompileInput): ExecResult {
86118
maxLen = mLen
87119
}
88120
const Gquaddivisor = opts._common.param('gasPrices', 'modexpGquaddivisor')
89-
const gasUsed = adjustedELen.mul(multComplexity(maxLen)).divn(Gquaddivisor)
121+
let gasUsed
122+
123+
const bStart = new BN(96)
124+
const bEnd = bStart.add(bLen)
125+
const eStart = bEnd
126+
const eEnd = eStart.add(eLen)
127+
const mStart = eEnd
128+
const mEnd = mStart.add(mLen)
129+
130+
const maxInt = new BN(Number.MAX_SAFE_INTEGER)
131+
const maxSize = new BN(2147483647) // ethereumjs-util setLengthRight limitation
132+
133+
if (bLen.gt(maxSize) || eLen.gt(maxSize) || mLen.gt(maxSize)) {
134+
return OOGResult(opts.gasLimit)
135+
}
136+
137+
if (mEnd.gt(maxInt)) {
138+
return OOGResult(opts.gasLimit)
139+
}
140+
141+
const B = new BN(setLengthRight(data.slice(bStart.toNumber(), bEnd.toNumber()), bLen.toNumber()))
142+
const E = new BN(setLengthRight(data.slice(eStart.toNumber(), eEnd.toNumber()), eLen.toNumber()))
143+
const M = new BN(setLengthRight(data.slice(mStart.toNumber(), mEnd.toNumber()), mLen.toNumber()))
144+
145+
if (!opts._common.eips().includes(2565)) {
146+
gasUsed = adjustedELen.mul(multComplexity(maxLen)).divn(Gquaddivisor)
147+
} else {
148+
gasUsed = calculateGasEIP2565(bLen, eLen, mLen, E)
149+
}
90150

91151
if (opts.gasLimit.lt(gasUsed)) {
92152
return OOGResult(opts.gasLimit)
@@ -106,28 +166,6 @@ export default function (opts: PrecompileInput): ExecResult {
106166
}
107167
}
108168

109-
const maxInt = new BN(Number.MAX_SAFE_INTEGER)
110-
const maxSize = new BN(2147483647) // ethereumjs-util setLengthRight limitation
111-
112-
if (bLen.gt(maxSize) || eLen.gt(maxSize) || mLen.gt(maxSize)) {
113-
return OOGResult(opts.gasLimit)
114-
}
115-
116-
const bStart = new BN(96)
117-
const bEnd = bStart.add(bLen)
118-
const eStart = bEnd
119-
const eEnd = eStart.add(eLen)
120-
const mStart = eEnd
121-
const mEnd = mStart.add(mLen)
122-
123-
if (mEnd.gt(maxInt)) {
124-
return OOGResult(opts.gasLimit)
125-
}
126-
127-
const B = new BN(setLengthRight(data.slice(bStart.toNumber(), bEnd.toNumber()), bLen.toNumber()))
128-
const E = new BN(setLengthRight(data.slice(eStart.toNumber(), eEnd.toNumber()), eLen.toNumber()))
129-
const M = new BN(setLengthRight(data.slice(mStart.toNumber(), mEnd.toNumber()), mLen.toNumber()))
130-
131169
let R
132170
if (M.isZero()) {
133171
R = new BN(0)

packages/vm/lib/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export default class VM extends AsyncEventEmitter {
163163

164164
if (opts.common) {
165165
//EIPs
166-
const supportedEIPs = [2537, 2929]
166+
const supportedEIPs = [2537, 2565, 2929]
167167
for (const eip of opts.common.eips()) {
168168
if (!supportedEIPs.includes(eip)) {
169169
throw new Error(`${eip} is not supported by the VM`)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import tape from 'tape'
2+
import { Address, BN } from 'ethereumjs-util'
3+
import Common from '@ethereumjs/common'
4+
import VM from '../../../lib'
5+
6+
// See https://github.com/holiman/go-ethereum/blob/2c99023b68c573ba24a5b01db13e000bd9b82417/core/vm/testdata/precompiles/modexp_eip2565.json
7+
const testData = require('./eip-2565-testdata.json')
8+
9+
tape('EIP-2565 ModExp gas cost tests', (t) => {
10+
t.test('Test return data, gas cost and execution status against testdata', async (st) => {
11+
const common = new Common({ chain: 'mainnet', hardfork: 'byzantium', eips: [2565] })
12+
const vm = new VM({ common: common })
13+
14+
for (const test of testData) {
15+
const testName = test.Name
16+
const to = new Address(Buffer.from('0000000000000000000000000000000000000005', 'hex'))
17+
const result = await vm.runCall({
18+
caller: Address.zero(),
19+
gasLimit: new BN(0xffffffffff),
20+
to,
21+
value: new BN(0),
22+
data: Buffer.from(test.Input, 'hex'),
23+
})
24+
25+
if (!result.execResult.gasUsed.eq(new BN(test.Gas))) {
26+
st.fail(
27+
`[${testName}]: Gas usage incorrect, expected ${
28+
test.Gas
29+
}, got ${result.execResult.gasUsed.toNumber()}`
30+
)
31+
continue
32+
}
33+
34+
if (result.execResult.exceptionError) {
35+
st.fail(`[${testName}]: Call should not fail`)
36+
continue
37+
}
38+
39+
if (!result.execResult.returnValue.equals(Buffer.from(test.Expected, 'hex'))) {
40+
st.fail(`[${testName}]: Return value not the expected value`)
41+
continue
42+
}
43+
44+
st.pass(`[${testName}]: Call produced the expected results`)
45+
}
46+
47+
st.end()
48+
})
49+
})

0 commit comments

Comments
 (0)