-
Notifications
You must be signed in to change notification settings - Fork 759
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #889 from ethereumjs/eip-2929
EIP-2929: Gas cost increases for state access opcodes
- Loading branch information
Showing
11 changed files
with
537 additions
and
141 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "EIP-2929", | ||
"comment": "Gas cost increases for state access opcodes", | ||
"url": "https://eips.ethereum.org/EIPS/eip-2929", | ||
"status": "Draft", | ||
"minimumHardfork": "chainstart", | ||
"gasConfig": {}, | ||
"gasPrices": { | ||
"coldsload": { | ||
"v": 2100, | ||
"d": "Gas cost of the first read of storage from a given location (per transaction)" | ||
}, | ||
"coldaccountaccess": { | ||
"v": 2600, | ||
"d": "Gas cost of the first read of a given address (per transaction)" | ||
}, | ||
"warmstorageread": { | ||
"v": 100, | ||
"d": "Gas cost of reading storage locations which have already loaded 'cold'" | ||
} | ||
}, | ||
"vm": {}, | ||
"pow": {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import BN = require('bn.js') | ||
import { RunState } from './../interpreter' | ||
|
||
/** | ||
* Adjusts gas usage and refunds of SStore ops per EIP-1283 (Constantinople) | ||
* | ||
* @param {RunState} runState | ||
* @param {any} found | ||
* @param {Buffer} value | ||
*/ | ||
export function updateSstoreGasEIP1283(runState: RunState, found: any, value: Buffer, key: Buffer) { | ||
if (runState._common.hardfork() === 'constantinople') { | ||
const original = found.original | ||
const current = found.current | ||
if (current.equals(value)) { | ||
// If current value equals new value (this is a no-op), 200 gas is deducted. | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreNoopGas'))) | ||
return | ||
} | ||
// If current value does not equal new value | ||
if (original.equals(current)) { | ||
// If original value equals current value (this storage slot has not been changed by the current execution context) | ||
if (original.length === 0) { | ||
// If original value is 0, 20000 gas is deducted. | ||
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreInitGas'))) | ||
} | ||
if (value.length === 0) { | ||
// If new value is 0, add 15000 gas to refund counter. | ||
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund'))) | ||
} | ||
// Otherwise, 5000 gas is deducted. | ||
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreCleanGas'))) | ||
} | ||
// If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses. | ||
if (original.length !== 0) { | ||
// If original value is not 0 | ||
if (current.length === 0) { | ||
// 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. | ||
runState.eei.subRefund(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund'))) | ||
} else if (value.length === 0) { | ||
// If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter. | ||
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreClearRefund'))) | ||
} | ||
} | ||
if (original.equals(value)) { | ||
// If original value equals new value (this storage slot is reset) | ||
if (original.length === 0) { | ||
// If original value is 0, add 19800 gas to refund counter. | ||
runState.eei.refundGas( | ||
new BN(runState._common.param('gasPrices', 'netSstoreResetClearRefund')) | ||
) | ||
} else { | ||
// Otherwise, add 4800 gas to refund counter. | ||
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'netSstoreResetRefund'))) | ||
} | ||
} | ||
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas'))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import BN = require('bn.js') | ||
import { RunState } from './../interpreter' | ||
import { ERROR } from '../../exceptions' | ||
import { adjustSstoreGasEIP2929 } from './EIP2929' | ||
import { trap } from './util' | ||
|
||
/** | ||
* Adjusts gas usage and refunds of SStore ops per EIP-2200 (Istanbul) | ||
* | ||
* @param {RunState} runState | ||
* @param {any} found | ||
* @param {Buffer} value | ||
*/ | ||
export function updateSstoreGasEIP2200(runState: RunState, found: any, value: Buffer, key: Buffer) { | ||
if (runState._common.gteHardfork('istanbul')) { | ||
const original = found.original | ||
const current = found.current | ||
// Fail if not enough gas is left | ||
if ( | ||
runState.eei.getGasLeft().lten(runState._common.param('gasPrices', 'sstoreSentryGasEIP2200')) | ||
) { | ||
trap(ERROR.OUT_OF_GAS) | ||
} | ||
|
||
// Noop | ||
if (current.equals(value)) { | ||
const sstoreNoopCost = runState._common.param('gasPrices', 'sstoreNoopGasEIP2200') | ||
return runState.eei.useGas( | ||
new BN(adjustSstoreGasEIP2929(runState, key, sstoreNoopCost, 'noop')) | ||
) | ||
} | ||
if (original.equals(current)) { | ||
// Create slot | ||
if (original.length === 0) { | ||
return runState.eei.useGas( | ||
new BN(runState._common.param('gasPrices', 'sstoreInitGasEIP2200')) | ||
) | ||
} | ||
// Delete slot | ||
if (value.length === 0) { | ||
runState.eei.refundGas( | ||
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')) | ||
) | ||
} | ||
// Write existing slot | ||
return runState.eei.useGas( | ||
new BN(runState._common.param('gasPrices', 'sstoreCleanGasEIP2200')) | ||
) | ||
} | ||
if (original.length > 0) { | ||
if (current.length === 0) { | ||
// Recreate slot | ||
runState.eei.subRefund( | ||
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')) | ||
) | ||
} else if (value.length === 0) { | ||
// Delete slot | ||
runState.eei.refundGas( | ||
new BN(runState._common.param('gasPrices', 'sstoreClearRefundEIP2200')) | ||
) | ||
} | ||
} | ||
if (original.equals(value)) { | ||
if (original.length === 0) { | ||
// Reset to original non-existent slot | ||
const sstoreInitRefund = runState._common.param('gasPrices', 'sstoreInitRefundEIP2200') | ||
runState.eei.refundGas( | ||
new BN(adjustSstoreGasEIP2929(runState, key, sstoreInitRefund, 'initRefund')) | ||
) | ||
} else { | ||
// Reset to original existing slot | ||
const sstoreCleanRefund = runState._common.param('gasPrices', 'sstoreCleanRefundEIP2200') | ||
runState.eei.refundGas( | ||
new BN(adjustSstoreGasEIP2929(runState, key, sstoreCleanRefund, 'cleanRefund')) | ||
) | ||
} | ||
} | ||
// Dirty update | ||
return runState.eei.useGas(new BN(runState._common.param('gasPrices', 'sstoreDirtyGasEIP2200'))) | ||
} else { | ||
const sstoreResetCost = runState._common.param('gasPrices', 'sstoreReset') | ||
if (value.length === 0 && !found.length) { | ||
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset'))) | ||
} else if (value.length === 0 && found.length) { | ||
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset'))) | ||
runState.eei.refundGas(new BN(runState._common.param('gasPrices', 'sstoreRefund'))) | ||
} else if (value.length !== 0 && !found.length) { | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'sstoreSet'))) | ||
} else if (value.length !== 0 && found.length) { | ||
runState.eei.useGas(new BN(adjustSstoreGasEIP2929(runState, key, sstoreResetCost, 'reset'))) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import BN = require('bn.js') | ||
import { Address } from 'ethereumjs-util' | ||
import { RunState } from './../interpreter' | ||
import { addressToBuffer } from './util' | ||
|
||
/** | ||
* Adds address to accessedAddresses set if not already included. | ||
* Adjusts cost incurred for executing opcode based on whether address read | ||
* is warm/cold. (EIP 2929) | ||
* @param {RunState} runState | ||
* @param {BN} address | ||
*/ | ||
export function accessAddressEIP2929(runState: RunState, address: Address, baseFee?: number) { | ||
if (!runState._common.eips().includes(2929)) return | ||
|
||
const addressStr = address.toString() | ||
|
||
// Cold | ||
if (!runState.accessedAddresses.has(addressStr)) { | ||
runState.accessedAddresses.add(addressStr) | ||
|
||
// CREATE, CREATE2 opcodes have the address warmed for free. | ||
// selfdestruct beneficiary address reads are charged an *additional* cold access | ||
if (baseFee !== undefined) { | ||
runState.eei.useGas( | ||
new BN(runState._common.param('gasPrices', 'coldaccountaccess') - baseFee) | ||
) | ||
} | ||
// Warm: (selfdestruct beneficiary address reads are not charged when warm) | ||
} else if (baseFee !== undefined && baseFee > 0) { | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'warmstorageread') - baseFee)) | ||
} | ||
} | ||
|
||
/** | ||
* Adds (address, key) to accessedStorage tuple set if not already included. | ||
* Adjusts cost incurred for executing opcode based on whether storage read | ||
* is warm/cold. (EIP 2929) | ||
* @param {RunState} runState | ||
* @param {Buffer} key (to storage slot) | ||
*/ | ||
export function accessStorageEIP2929(runState: RunState, key: Buffer, isSstore: boolean) { | ||
if (!runState._common.eips().includes(2929)) return | ||
|
||
const keyStr = key.toString('hex') | ||
const baseFee = !isSstore ? runState._common.param('gasPrices', 'sload') : 0 | ||
const address = runState.eei.getAddress().toString() | ||
const keysAtAddress = runState.accessedStorage.get(address) | ||
|
||
// Cold (SLOAD and SSTORE) | ||
if (!keysAtAddress) { | ||
runState.accessedStorage.set(address, new Set()) | ||
// @ts-ignore Set Object is possibly 'undefined' | ||
runState.accessedStorage.get(address).add(keyStr) | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'coldsload') - baseFee)) | ||
} else if (keysAtAddress && !keysAtAddress.has(keyStr)) { | ||
keysAtAddress.add(keyStr) | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'coldsload') - baseFee)) | ||
// Warm (SLOAD only) | ||
} else if (!isSstore) { | ||
runState.eei.useGas(new BN(runState._common.param('gasPrices', 'warmstorageread') - baseFee)) | ||
} | ||
} | ||
|
||
/** | ||
* Adjusts cost of SSTORE_RESET_GAS or SLOAD (aka sstorenoop) (EIP-2200) downward when storage | ||
* location is already warm | ||
* @param {RunState} runState | ||
* @param {Buffer} key storage slot | ||
* @param {number} defaultCost SSTORE_RESET_GAS / SLOAD | ||
* @param {string} costName parameter name ('reset' or 'noop') | ||
* @return {number} adjusted cost | ||
*/ | ||
export function adjustSstoreGasEIP2929( | ||
runState: RunState, | ||
key: Buffer, | ||
defaultCost: number, | ||
costName: string | ||
): number { | ||
if (!runState._common.eips().includes(2929)) return defaultCost | ||
|
||
const keyStr = key.toString('hex') | ||
const address = runState.eei.getAddress().toString() | ||
const warmRead = runState._common.param('gasPrices', 'warmstorageread') | ||
const coldSload = runState._common.param('gasPrices', 'coldsload') | ||
|
||
// @ts-ignore Set Object is possibly 'undefined' | ||
if (runState.accessedStorage.has(address) && runState.accessedStorage.get(address).has(keyStr)) { | ||
switch (costName) { | ||
case 'reset': | ||
return defaultCost - coldSload | ||
case 'noop': | ||
return warmRead | ||
case 'initRefund': | ||
return runState._common.param('gasPrices', 'sstoreInitGasEIP2200') - warmRead | ||
case 'cleanRefund': | ||
return runState._common.param('gasPrices', 'sstoreReset') - coldSload - warmRead | ||
} | ||
} | ||
|
||
return defaultCost | ||
} |
Oops, something went wrong.