-
Notifications
You must be signed in to change notification settings - Fork 772
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
EIP 1283 SSTORE #367
EIP 1283 SSTORE #367
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -374,19 +374,10 @@ module.exports = { | |
value = val.toArrayLike(Buffer, 'be') | ||
} | ||
|
||
stateManager.getContractStorage(runState.address, key, function (err, found) { | ||
getContractStorage(runState, address, key, function (err, found) { | ||
if (err) return cb(err) | ||
try { | ||
if (value.length === 0 && !found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
} else if (value.length === 0 && found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
runState.gasRefund.iaddn(runState._common.param('gasPrices', 'sstoreRefund')) | ||
} else if (value.length !== 0 && !found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreSet'))) | ||
} else if (value.length !== 0 && found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
} | ||
updateSstoreGas(runState, found, value) | ||
} catch (e) { | ||
cb(e.error) | ||
return | ||
|
@@ -1024,3 +1015,73 @@ function makeCall (runState, callOptions, localOpts, cb) { | |
} | ||
} | ||
} | ||
|
||
function getContractStorage (runState, address, key, cb) { | ||
if (runState._common.gteHardfork('constantinople')) { | ||
async.parallel({ | ||
original: function (cb) { runState.originalState.getContractStorage(address, key, cb) }, | ||
current: function (cb) { runState.stateManager.getContractStorage(address, key, cb) } | ||
}, cb) | ||
} else { | ||
runState.stateManager.getContractStorage(address, key, cb) | ||
} | ||
} | ||
|
||
function updateSstoreGas (runState, found, value) { | ||
if (runState._common.gteHardfork('constantinople')) { | ||
var original = found.original | ||
var current = found.current | ||
if (current.equals(value)) { | ||
// If current value equals new value (this is a no-op), 200 gas is deducted. | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreNoopGas'))) | ||
return | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. First case of EIP, with return, ok. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Second look, as far as I see it these both versions 1) first subGas() call, then blank return and 2) return subGas() on other occasions doesn't make any difference I assume (?). Won't make this a review blocker, but unless there are reasons for it I am not seeing right now this might be updated to be more consistent if there is the occasion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! This should be |
||
// 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 subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreInitGas'))) | ||
} | ||
if (value.length === 0) { | ||
// If new value is 0, add 15000 gas to refund counter. | ||
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreClearRefund')) | ||
} | ||
// Otherwise, 5000 gas is deducted. | ||
return subGas(runState, 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.gasRefund = runState.gasRefund.subn(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.gasRefund = runState.gasRefund.addn(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.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreResetClearRefund')) | ||
} else { | ||
// Otherwise, add 4800 gas to refund counter. | ||
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreResetRefund')) | ||
} | ||
} | ||
return subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas'))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is working down the various cases from the EIP and adding to the existing If there are smaller caveats or edge cases we can do subsequent PRs to correct once we have a clearer picture on the test runs and tests have stabilized more. |
||
} else { | ||
if (value.length === 0 && !found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
} else if (value.length === 0 && found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
runState.gasRefund.iaddn(runState._common.param('gasPrices', 'sstoreRefund')) | ||
} else if (value.length !== 0 && !found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreSet'))) | ||
} else if (value.length !== 0 && found.length) { | ||
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset'))) | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, clean separation of pre-constantinople SSTORE gas calculations. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
const tape = require('tape') | ||
const async = require('async') | ||
const VM = require('../') | ||
const Account = require('ethereumjs-account') | ||
const testUtil = require('./util') | ||
const Trie = require('merkle-patricia-tree/secure') | ||
const ethUtil = require('ethereumjs-util') | ||
const BN = ethUtil.BN | ||
|
||
var testCases = [ | ||
{ code: '0x60006000556000600055', usedGas: 412, refund: 0, original: '0x' }, | ||
{ code: '0x60006000556001600055', usedGas: 20212, refund: 0, original: '0x' }, | ||
{ code: '0x60016000556000600055', usedGas: 20212, refund: 19800, original: '0x' }, | ||
{ code: '0x60016000556002600055', usedGas: 20212, refund: 0, original: '0x' }, | ||
{ code: '0x60016000556001600055', usedGas: 20212, refund: 0, original: '0x' }, | ||
{ code: '0x60006000556000600055', usedGas: 5212, refund: 15000, original: '0x01' }, | ||
{ code: '0x60006000556001600055', usedGas: 5212, refund: 4800, original: '0x01' }, | ||
{ code: '0x60006000556002600055', usedGas: 5212, refund: 0, original: '0x01' }, | ||
{ code: '0x60026000556000600055', usedGas: 5212, refund: 15000, original: '0x01' }, | ||
{ code: '0x60026000556003600055', usedGas: 5212, refund: 0, original: '0x01' }, | ||
{ code: '0x60026000556001600055', usedGas: 5212, refund: 4800, original: '0x01' }, | ||
{ code: '0x60026000556002600055', usedGas: 5212, refund: 0, original: '0x01' }, | ||
{ code: '0x60016000556000600055', usedGas: 5212, refund: 15000, original: '0x01' }, | ||
{ code: '0x60016000556002600055', usedGas: 5212, refund: 0, original: '0x01' }, | ||
{ code: '0x60016000556001600055', usedGas: 412, refund: 0, original: '0x01' }, | ||
{ code: '0x600160005560006000556001600055', usedGas: 40218, refund: 19800, original: '0x' }, | ||
{ code: '0x600060005560016000556000600055', usedGas: 10218, refund: 19800, original: '0x01' } | ||
] | ||
|
||
var testData = { | ||
'env': { | ||
'currentCoinbase': '0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba', | ||
'currentDifficulty': '0x0100', | ||
'currentGasLimit': '0x0f4240', | ||
'currentNumber': '0x00', | ||
'currentTimestamp': '0x01' | ||
}, | ||
'exec': { | ||
'address': '0x01', | ||
'caller': '0xcd1722f3947def4cf144679da39c4c32bdc35681', | ||
'code': '0x60006000556000600055', | ||
'data': '0x', | ||
'gas': '0', | ||
'gasPrice': '0x5af3107a4000', | ||
'origin': '0xcd1722f3947def4cf144679da39c4c32bdc35681', | ||
'value': '0x0de0b6b3a7640000' | ||
}, | ||
'gas': '0', | ||
'pre': { | ||
'0x01': { | ||
'balance': '0x152d02c7e14af6800000', | ||
'code': '0x', | ||
'nonce': '0x00', | ||
'storage': { | ||
'0x': '0' | ||
} | ||
} | ||
} | ||
} | ||
|
||
tape('test constantinople SSTORE (eip-1283)', function (t) { | ||
testCases.forEach(function (params, i) { | ||
t.test('should correctly run eip-1283 test #' + i, function (st) { | ||
let state = new Trie() | ||
let results | ||
let account | ||
|
||
testData.exec.code = params.code | ||
testData.exec.gas = params.usedGas | ||
testData.pre['0x01'].storage['0x'] = params.original | ||
|
||
async.series([ | ||
function (done) { | ||
let acctData = testData.pre[testData.exec.address] | ||
account = new Account() | ||
account.nonce = testUtil.format(acctData.nonce) | ||
account.balance = testUtil.format(acctData.balance) | ||
testUtil.setupPreConditions(state, testData, done) | ||
}, | ||
function (done) { | ||
state.get(Buffer.from(testData.exec.address, 'hex'), function (err, data) { | ||
let a = new Account(data) | ||
account.stateRoot = a.stateRoot | ||
done(err) | ||
}) | ||
}, | ||
function (done) { | ||
let block = testUtil.makeBlockFromEnv(testData.env) | ||
let vm = new VM({state: state, hardfork: 'constantinople'}) | ||
let runCodeData = testUtil.makeRunCodeData(testData.exec, account, block) | ||
vm.runCode(runCodeData, function (err, r) { | ||
if (r) { | ||
results = r | ||
} | ||
done(err) | ||
}) | ||
}, | ||
function (done) { | ||
if (testData.gas) { | ||
let actualGas = results.gas.toString() | ||
let expectedGas = new BN(testUtil.format(testData.gas)).toString() | ||
t.equal(actualGas, expectedGas, 'valid gas usage') | ||
t.equals(results.gasRefund.toNumber(), params.refund, 'valid gas refund') | ||
} | ||
done() | ||
} | ||
], function (err) { | ||
t.assert(!err) | ||
st.end() | ||
}) | ||
}) | ||
}) | ||
}) |
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.
Pre-constantinople logic seems to be cleanly separated, ok.