-
Notifications
You must be signed in to change notification settings - Fork 809
EIP-2929: Gas cost increases for state access opcodes #889
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
cca37a6
Initial draft
cgewecke 582d625
Adjust EIP-2200 refunds
cgewecke 52c7f96
Add charges for selfdestruct ETH recipient address reads
cgewecke c93eef7
Fix errors from file reorg rebase
cgewecke 2e06aaa
Move dynamic gas pricing logic into own EIP files
cgewecke 0ef9686
Fix tape import (for config 2.0 rebase)
cgewecke f82db4a
Update supported EIPs comment in lib/index
cgewecke 20e845d
Lint fixes (config 2.0)
cgewecke c186e33
vm -> EIP2929: updated accessAddressEIP2929 to use Address type, test…
holgerd77 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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 hidden or 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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just seeing here that this implementation has also these
common.eips().includes()
calls. We should really replace this soon by a method in common also taking the HFs into account. This will otherwise fall onto our feet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(so "soon" means: in the next 1-2 weeks)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to introduce this in the EIP2718 PR, but won't do it as it will get too bloated otherwise. Can you open an issue for this so we don't "forget" it? 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will just prioritize this as the first task I'll do on Monday, so we won't forget then. 😄