Skip to content

Commit

Permalink
EIP 1283 SSTORE
Browse files Browse the repository at this point in the history
  • Loading branch information
vpulim committed Oct 10, 2018
1 parent 08b5a93 commit 8e527e9
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 12 deletions.
83 changes: 72 additions & 11 deletions lib/opFns.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
// 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')))
} 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')))
}
}
}
1 change: 1 addition & 0 deletions lib/runCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ module.exports = function (opts, cb) {
var runState = {
blockchain: self.blockchain,
stateManager: stateManager,
originalState: stateManager.copy(),
returnValue: false,
stopped: false,
vmError: false,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"async-eventemitter": "^0.2.2",
"ethereumjs-account": "^2.0.3",
"ethereumjs-block": "~2.0.1",
"ethereumjs-common": "~0.4.0",
"ethereumjs-common": "ethereumjs/ethereumjs-common#eip-1283",
"ethereumjs-util": "^6.0.0",
"fake-merkle-patricia-tree": "^1.0.1",
"functional-red-black-tree": "^1.0.1",
Expand Down
113 changes: 113 additions & 0 deletions tests/constantinopleSstoreTest.js
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()
})
})
})
})
1 change: 1 addition & 0 deletions tests/tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ function runAll () {
require('./tester.js')
require('./cacheTest.js')
require('./genesishashes.js')
require('./constantinopleSstoreTest.js')
async.series([
// runTests.bind(this, 'VMTests', {}), // VM tests disabled since we don't support Frontier gas costs
runTests.bind(this, 'GeneralStateTests', {}),
Expand Down

0 comments on commit 8e527e9

Please sign in to comment.