Skip to content

Commit 8cdc9b0

Browse files
committed
Move dynamic gas pricing logic into own EIP files
1 parent 1959de5 commit 8cdc9b0

File tree

5 files changed

+259
-238
lines changed

5 files changed

+259
-238
lines changed
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import BN = require('bn.js')
2+
import { RunState } from './../interpreter'
3+
4+
/**
5+
* Adjusts gas usage and refunds of SStore ops per EIP-1283 (Constantinople)
6+
*
7+
* @param {RunState} runState
8+
* @param {any} found
9+
* @param {Buffer} value
10+
*/
11+
export function updateSstoreGasEIP1283(runState: RunState, found: any, value: Buffer, key: Buffer) {
12+
if (runState._common.hardfork() === 'constantinople') {
13+
const original = found.original
14+
const current = found.current
15+
if (current.equals(value)) {
16+
// If current value equals new value (this is a no-op), 200 gas is deducted.
17+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreNoopGas')))
18+
return
19+
}
20+
// If current value does not equal new value
21+
if (original.equals(current)) {
22+
// If original value equals current value (this storage slot has not been changed by the current execution context)
23+
if (original.length === 0) {
24+
// If original value is 0, 20000 gas is deducted.
25+
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreInitGas')))
26+
}
27+
if (value.length === 0) {
28+
// If new value is 0, add 15000 gas to refund counter.
29+
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')))
30+
}
31+
// Otherwise, 5000 gas is deducted.
32+
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreCleanGas')))
33+
}
34+
// If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
35+
if (original.length !== 0) {
36+
// If original value is not 0
37+
if (current.length === 0) {
38+
// If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0.
39+
runState.eei.subRefund(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')))
40+
} else if (value.length === 0) {
41+
// If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
42+
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund')))
43+
}
44+
}
45+
if (original.equals(value)) {
46+
// If original value equals new value (this storage slot is reset)
47+
if (original.length === 0) {
48+
// If original value is 0, add 19800 gas to refund counter.
49+
runState.eei.refundGas(
50+
new BN(runState._common.param('gasPrices', 'netSstoreResetClearRefund')),
51+
)
52+
} else {
53+
// Otherwise, add 4800 gas to refund counter.
54+
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreResetRefund')))
55+
}
56+
}
57+
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas')))
58+
}
59+
}
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import BN = require('bn.js')
2+
import { RunState } from './../interpreter'
3+
import { ERROR } from '../../exceptions'
4+
import { adjustSstoreGasEIP2929 } from './EIP2929'
5+
import { trap } from './util'
6+
7+
/**
8+
* Adjusts gas usage and refunds of SStore ops per EIP-2200 (Istanbul)
9+
*
10+
* @param {RunState} runState
11+
* @param {any} found
12+
* @param {Buffer} value
13+
*/
14+
export function updateSstoreGasEIP2200(runState: RunState, found: any, value: Buffer, key: Buffer) {
15+
if (runState._common.gteHardfork('istanbul')) {
16+
const original = found.original
17+
const current = found.current
18+
// Fail if not enough gas is left
19+
if (
20+
runState.eei.getGasLeft().lten(runState._common.param('gasPrices', 'sstoreSentryGasEIP2200'))
21+
) {
22+
trap(ERROR.OUT_OF_GAS)
23+
}
24+
25+
// Noop
26+
if (current.equals(value)) {
27+
const sstoreNoopCost = runState._common.param('gasPrices', 'sstoreNoopGasEIP2200')
28+
return runState.eei.useGas(
29+
new BN(adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop')),
30+
)
31+
}
32+
if (original.equals(current)) {
33+
// Create slot
34+
if (original.length === 0) {
35+
return runState.eei.useGas(
36+
new BN(runState._common.param('gasPrices', 'sstoreInitGasEIP2200')),
37+
)
38+
}
39+
// Delete slot
40+
if (value.length === 0) {
41+
runState.eei.refundGas(
42+
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
43+
)
44+
}
45+
// Write existing slot
46+
return runState.eei.useGas(
47+
new BN(runState._common.param('gasPrices', 'sstoreCleanGasEIP2200')),
48+
)
49+
}
50+
if (original.length > 0) {
51+
if (current.length === 0) {
52+
// Recreate slot
53+
runState.eei.subRefund(
54+
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
55+
)
56+
} else if (value.length === 0) {
57+
// Delete slot
58+
runState.eei.refundGas(
59+
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')),
60+
)
61+
}
62+
}
63+
if (original.equals(value)) {
64+
if (original.length === 0) {
65+
// Reset to original non-existent slot
66+
const sstoreInitRefund = runState._common.param('gasPrices', 'sstoreInitRefundEIP2200')
67+
runState.eei.refundGas(
68+
new BN(adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund')),
69+
)
70+
} else {
71+
// Reset to original existing slot
72+
const sstoreCleanRefund = runState._common.param('gasPrices', 'sstoreCleanRefundEIP2200')
73+
runState.eei.refundGas(
74+
new BN(adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund')),
75+
)
76+
}
77+
}
78+
// Dirty update
79+
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'sstoreDirtyGasEIP2200')))
80+
} else {
81+
const sstoreResetCost = runState._common.param('gasPrices', 'sstoreReset')
82+
if (value.length === 0 && !found.length) {
83+
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset')))
84+
} else if (value.length === 0 && found.length) {
85+
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset')))
86+
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'sstoreRefund')))
87+
} else if (value.length !== 0 && !found.length) {
88+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'sstoreSet')))
89+
} else if (value.length !== 0 && found.length) {
90+
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset')))
91+
}
92+
}
93+
}
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import BN = require('bn.js')
2+
import { RunState } from './../interpreter'
3+
import { addressToBuffer } from './util'
4+
5+
/**
6+
* Adds address to accessedAddresses set if not already included.
7+
* Adjusts cost incurred for executing opcode based on whether address read
8+
* is warm/cold. (EIP 2929)
9+
* @param {RunState} runState
10+
* @param {BN} address
11+
*/
12+
export function accessAddressEIP2929(runState: RunState, address: BN | Buffer, baseFee?: number) {
13+
if (!runState._common.eips().includes(2929)) return
14+
15+
const addressStr = addressToBuffer(address).toString('hex')
16+
17+
// Cold
18+
if (!runState.accessedAddresses.has(addressStr)) {
19+
runState.accessedAddresses.add(addressStr)
20+
21+
// CREATE, CREATE2 opcodes have the address warmed for free.
22+
// selfdestruct beneficiary address reads are charged an *additional* cold access
23+
if (baseFee !== undefined) {
24+
runState.eei.useGas(
25+
new BN(runState._common.param('gasPrices', 'coldaccountaccess') - baseFee),
26+
)
27+
}
28+
// Warm: (selfdestruct beneficiary address reads are not charged when warm)
29+
} else if (baseFee !== undefined && baseFee > 0) {
30+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'warmstorageread') - baseFee))
31+
}
32+
}
33+
34+
/**
35+
* Adds (address, key) to accessedStorage tuple set if not already included.
36+
* Adjusts cost incurred for executing opcode based on whether storage read
37+
* is warm/cold. (EIP 2929)
38+
* @param {RunState} runState
39+
* @param {Buffer} key (to storage slot)
40+
*/
41+
export function accessStorageEIP2929(runState: RunState, key: Buffer, isSstore: boolean) {
42+
if (!runState._common.eips().includes(2929)) return
43+
44+
const keyStr = key.toString('hex')
45+
const baseFee = !isSstore ? runState._common.param('gasPrices', 'sload') : 0
46+
const address = runState.eei.getAddress().toString('hex')
47+
const keysAtAddress = runState.accessedStorage.get(address)
48+
49+
// Cold (SLOAD and SSTORE)
50+
if (!keysAtAddress) {
51+
runState.accessedStorage.set(address, new Set())
52+
// @ts-ignore Set Object is possibly 'undefined'
53+
runState.accessedStorage.get(address).add(keyStr)
54+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'coldsload') - baseFee))
55+
} else if (keysAtAddress && !keysAtAddress.has(keyStr)) {
56+
keysAtAddress.add(keyStr)
57+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'coldsload') - baseFee))
58+
// Warm (SLOAD only)
59+
} else if (!isSstore) {
60+
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'warmstorageread') - baseFee))
61+
}
62+
}
63+
64+
/**
65+
* Adjusts cost of SSTORE_RESET_GAS or SLOAD (aka sstorenoop) (EIP-2200) downward when storage
66+
* location is already warm
67+
* @param {RunState} runState
68+
* @param {Buffer} key storage slot
69+
* @param {number} defaultCost SSTORE_RESET_GAS / SLOAD
70+
* @param {string} costName parameter name ('reset' or 'noop')
71+
* @return {number} adjusted cost
72+
*/
73+
export function adjustSstoreGasEIP2929(
74+
runState: RunState,
75+
key: Buffer,
76+
defaultCost: number,
77+
costName: string,
78+
): number {
79+
if (!runState._common.eips().includes(2929)) return defaultCost
80+
81+
const keyStr = key.toString('hex')
82+
const address = runState.eei.getAddress().toString('hex')
83+
const warmRead = runState._common.param('gasPrices', 'warmstorageread')
84+
const coldSload = runState._common.param('gasPrices', 'coldsload')
85+
86+
// @ts-ignore Set Object is possibly 'undefined'
87+
if (runState.accessedStorage.has(address) && runState.accessedStorage.get(address).has(keyStr)) {
88+
switch (costName) {
89+
case 'reset':
90+
return defaultCost - coldSload
91+
case 'noop':
92+
return warmRead
93+
case 'initRefund':
94+
return runState._common.param('gasPrices', 'sstoreInitGasEIP2200') - warmRead
95+
case 'cleanRefund':
96+
return runState._common.param('gasPrices', 'sstoreReset') - coldSload - warmRead
97+
}
98+
}
99+
100+
return defaultCost
101+
}

packages/vm/lib/evm/opcodes/functions.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@ import {
88
KECCAK256_NULL,
99
} from 'ethereumjs-util'
1010
import {
11-
accessAddressEIP2929,
12-
accessStorageEIP2929,
13-
adjustSstoreGasEIP2929,
1411
addressToBuffer,
1512
describeLocation,
1613
divCeil,
@@ -21,10 +18,12 @@ import {
2118
maxCallGas,
2219
setLengthLeftStorage,
2320
subMemUsage,
24-
updateSstoreGas,
2521
trap,
2622
writeCallOutput,
2723
} from './util'
24+
import { updateSstoreGasEIP1283 } from './EIP1283'
25+
import { updateSstoreGasEIP2200 } from './EIP2200'
26+
import { accessAddressEIP2929, accessStorageEIP2929 } from './EIP2929'
2827
import { ERROR } from '../../exceptions'
2928
import { RunState } from './../interpreter'
3029

@@ -743,7 +742,8 @@ export const handlers: Map<number, OpHandler> = new Map([
743742
// TODO: Replace getContractStorage with EEI method
744743
const found = await getContractStorage(runState, runState.eei.getAddress(), keyBuf)
745744
accessStorageEIP2929(runState, keyBuf, true)
746-
updateSstoreGas(runState, found, setLengthLeftStorage(value), keyBuf)
745+
updateSstoreGasEIP1283(runState, found, setLengthLeftStorage(value), keyBuf)
746+
updateSstoreGasEIP2200(runState, found, setLengthLeftStorage(value), keyBuf)
747747
await runState.eei.storageStore(keyBuf, value)
748748
},
749749
],

0 commit comments

Comments
 (0)