Skip to content

Commit 7e15b49

Browse files
authored
Merge pull request ethereumjs#367 from ethereumjs/eip-1283
EIP 1283 SSTORE
2 parents 08b5a93 + 397d510 commit 7e15b49

File tree

5 files changed

+188
-12
lines changed

5 files changed

+188
-12
lines changed

lib/opFns.js

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -374,19 +374,10 @@ module.exports = {
374374
value = val.toArrayLike(Buffer, 'be')
375375
}
376376

377-
stateManager.getContractStorage(runState.address, key, function (err, found) {
377+
getContractStorage(runState, address, key, function (err, found) {
378378
if (err) return cb(err)
379379
try {
380-
if (value.length === 0 && !found.length) {
381-
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
382-
} else if (value.length === 0 && found.length) {
383-
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
384-
runState.gasRefund.iaddn(runState._common.param('gasPrices', 'sstoreRefund'))
385-
} else if (value.length !== 0 && !found.length) {
386-
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreSet')))
387-
} else if (value.length !== 0 && found.length) {
388-
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
389-
}
380+
updateSstoreGas(runState, found, value)
390381
} catch (e) {
391382
cb(e.error)
392383
return
@@ -1024,3 +1015,73 @@ function makeCall (runState, callOptions, localOpts, cb) {
10241015
}
10251016
}
10261017
}
1018+
1019+
function getContractStorage (runState, address, key, cb) {
1020+
if (runState._common.gteHardfork('constantinople')) {
1021+
async.parallel({
1022+
original: function (cb) { runState.originalState.getContractStorage(address, key, cb) },
1023+
current: function (cb) { runState.stateManager.getContractStorage(address, key, cb) }
1024+
}, cb)
1025+
} else {
1026+
runState.stateManager.getContractStorage(address, key, cb)
1027+
}
1028+
}
1029+
1030+
function updateSstoreGas (runState, found, value) {
1031+
if (runState._common.gteHardfork('constantinople')) {
1032+
var original = found.original
1033+
var current = found.current
1034+
if (current.equals(value)) {
1035+
// If current value equals new value (this is a no-op), 200 gas is deducted.
1036+
subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreNoopGas')))
1037+
return
1038+
}
1039+
// If current value does not equal new value
1040+
if (original.equals(current)) {
1041+
// If original value equals current value (this storage slot has not been changed by the current execution context)
1042+
if (original.length === 0) {
1043+
// If original value is 0, 20000 gas is deducted.
1044+
return subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreInitGas')))
1045+
}
1046+
if (value.length === 0) {
1047+
// If new value is 0, add 15000 gas to refund counter.
1048+
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreClearRefund'))
1049+
}
1050+
// Otherwise, 5000 gas is deducted.
1051+
return subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreCleanGas')))
1052+
}
1053+
// If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
1054+
if (original.length !== 0) {
1055+
// If original value is not 0
1056+
if (current.length === 0) {
1057+
// 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.
1058+
runState.gasRefund = runState.gasRefund.subn(runState._common.param('gasPrices', 'netSstoreClearRefund'))
1059+
} else if (value.length === 0) {
1060+
// If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
1061+
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreClearRefund'))
1062+
}
1063+
}
1064+
if (original.equals(value)) {
1065+
// If original value equals new value (this storage slot is reset)
1066+
if (original.length === 0) {
1067+
// If original value is 0, add 19800 gas to refund counter.
1068+
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreResetClearRefund'))
1069+
} else {
1070+
// Otherwise, add 4800 gas to refund counter.
1071+
runState.gasRefund = runState.gasRefund.addn(runState._common.param('gasPrices', 'netSstoreResetRefund'))
1072+
}
1073+
}
1074+
return subGas(runState, new BN(runState._common.param('gasPrices', 'netSstoreDirtyGas')))
1075+
} else {
1076+
if (value.length === 0 && !found.length) {
1077+
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
1078+
} else if (value.length === 0 && found.length) {
1079+
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
1080+
runState.gasRefund.iaddn(runState._common.param('gasPrices', 'sstoreRefund'))
1081+
} else if (value.length !== 0 && !found.length) {
1082+
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreSet')))
1083+
} else if (value.length !== 0 && found.length) {
1084+
subGas(runState, new BN(runState._common.param('gasPrices', 'sstoreReset')))
1085+
}
1086+
}
1087+
}

lib/runCode.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ module.exports = function (opts, cb) {
4848
var runState = {
4949
blockchain: self.blockchain,
5050
stateManager: stateManager,
51+
originalState: stateManager.copy(),
5152
returnValue: false,
5253
stopped: false,
5354
vmError: false,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"async-eventemitter": "^0.2.2",
3939
"ethereumjs-account": "^2.0.3",
4040
"ethereumjs-block": "~2.0.1",
41-
"ethereumjs-common": "~0.4.0",
41+
"ethereumjs-common": "^0.6.0",
4242
"ethereumjs-util": "^6.0.0",
4343
"fake-merkle-patricia-tree": "^1.0.1",
4444
"functional-red-black-tree": "^1.0.1",

tests/constantinopleSstoreTest.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const tape = require('tape')
2+
const async = require('async')
3+
const VM = require('../')
4+
const Account = require('ethereumjs-account')
5+
const testUtil = require('./util')
6+
const Trie = require('merkle-patricia-tree/secure')
7+
const ethUtil = require('ethereumjs-util')
8+
const BN = ethUtil.BN
9+
10+
var testCases = [
11+
{ code: '0x60006000556000600055', usedGas: 412, refund: 0, original: '0x' },
12+
{ code: '0x60006000556001600055', usedGas: 20212, refund: 0, original: '0x' },
13+
{ code: '0x60016000556000600055', usedGas: 20212, refund: 19800, original: '0x' },
14+
{ code: '0x60016000556002600055', usedGas: 20212, refund: 0, original: '0x' },
15+
{ code: '0x60016000556001600055', usedGas: 20212, refund: 0, original: '0x' },
16+
{ code: '0x60006000556000600055', usedGas: 5212, refund: 15000, original: '0x01' },
17+
{ code: '0x60006000556001600055', usedGas: 5212, refund: 4800, original: '0x01' },
18+
{ code: '0x60006000556002600055', usedGas: 5212, refund: 0, original: '0x01' },
19+
{ code: '0x60026000556000600055', usedGas: 5212, refund: 15000, original: '0x01' },
20+
{ code: '0x60026000556003600055', usedGas: 5212, refund: 0, original: '0x01' },
21+
{ code: '0x60026000556001600055', usedGas: 5212, refund: 4800, original: '0x01' },
22+
{ code: '0x60026000556002600055', usedGas: 5212, refund: 0, original: '0x01' },
23+
{ code: '0x60016000556000600055', usedGas: 5212, refund: 15000, original: '0x01' },
24+
{ code: '0x60016000556002600055', usedGas: 5212, refund: 0, original: '0x01' },
25+
{ code: '0x60016000556001600055', usedGas: 412, refund: 0, original: '0x01' },
26+
{ code: '0x600160005560006000556001600055', usedGas: 40218, refund: 19800, original: '0x' },
27+
{ code: '0x600060005560016000556000600055', usedGas: 10218, refund: 19800, original: '0x01' }
28+
]
29+
30+
var testData = {
31+
'env': {
32+
'currentCoinbase': '0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba',
33+
'currentDifficulty': '0x0100',
34+
'currentGasLimit': '0x0f4240',
35+
'currentNumber': '0x00',
36+
'currentTimestamp': '0x01'
37+
},
38+
'exec': {
39+
'address': '0x01',
40+
'caller': '0xcd1722f3947def4cf144679da39c4c32bdc35681',
41+
'code': '0x60006000556000600055',
42+
'data': '0x',
43+
'gas': '0',
44+
'gasPrice': '0x5af3107a4000',
45+
'origin': '0xcd1722f3947def4cf144679da39c4c32bdc35681',
46+
'value': '0x0de0b6b3a7640000'
47+
},
48+
'gas': '0',
49+
'pre': {
50+
'0x01': {
51+
'balance': '0x152d02c7e14af6800000',
52+
'code': '0x',
53+
'nonce': '0x00',
54+
'storage': {
55+
'0x': '0'
56+
}
57+
}
58+
}
59+
}
60+
61+
tape('test constantinople SSTORE (eip-1283)', function (t) {
62+
testCases.forEach(function (params, i) {
63+
t.test('should correctly run eip-1283 test #' + i, function (st) {
64+
let state = new Trie()
65+
let results
66+
let account
67+
68+
testData.exec.code = params.code
69+
testData.exec.gas = params.usedGas
70+
testData.pre['0x01'].storage['0x'] = params.original
71+
72+
async.series([
73+
function (done) {
74+
let acctData = testData.pre[testData.exec.address]
75+
account = new Account()
76+
account.nonce = testUtil.format(acctData.nonce)
77+
account.balance = testUtil.format(acctData.balance)
78+
testUtil.setupPreConditions(state, testData, done)
79+
},
80+
function (done) {
81+
state.get(Buffer.from(testData.exec.address, 'hex'), function (err, data) {
82+
let a = new Account(data)
83+
account.stateRoot = a.stateRoot
84+
done(err)
85+
})
86+
},
87+
function (done) {
88+
let block = testUtil.makeBlockFromEnv(testData.env)
89+
let vm = new VM({state: state, hardfork: 'constantinople'})
90+
let runCodeData = testUtil.makeRunCodeData(testData.exec, account, block)
91+
vm.runCode(runCodeData, function (err, r) {
92+
if (r) {
93+
results = r
94+
}
95+
done(err)
96+
})
97+
},
98+
function (done) {
99+
if (testData.gas) {
100+
let actualGas = results.gas.toString()
101+
let expectedGas = new BN(testUtil.format(testData.gas)).toString()
102+
t.equal(actualGas, expectedGas, 'valid gas usage')
103+
t.equals(results.gasRefund.toNumber(), params.refund, 'valid gas refund')
104+
}
105+
done()
106+
}
107+
], function (err) {
108+
t.assert(!err)
109+
st.end()
110+
})
111+
})
112+
})
113+
})

tests/tester.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ function runAll () {
250250
require('./tester.js')
251251
require('./cacheTest.js')
252252
require('./genesishashes.js')
253+
require('./constantinopleSstoreTest.js')
253254
async.series([
254255
// runTests.bind(this, 'VMTests', {}), // VM tests disabled since we don't support Frontier gas costs
255256
runTests.bind(this, 'GeneralStateTests', {}),

0 commit comments

Comments
 (0)