Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Commit 710ac6e

Browse files
sorpaasandresilva
authored andcommitted
EIP 1283: Net gas metering for SSTORE without dirty maps (#9319)
* Implement last_checkpoint_storage_at * Add reverted_storage_at for externalities * sstore_clears_count -> sstore_clears_refund * Implement eip1283 for evm * Add eip1283Transition params * evm: fix tests * jsontests: fix test * Return checkpoint index when creating * Comply with spec Version II * Fix docs * Fix jsontests feature compile * Address grumbles * Fix no-checkpoint-entry case * Remove unnecessary expect * Add test for State::checkpoint_storage_at * Add executive level test for eip1283 * Hard-code transaction_checkpoint_index to 0 * Fix jsontests * Add tests for checkpoint discard/revert * Require checkpoint to be empty for kill_account and commit * Get code coverage * Use saturating_add/saturating_sub * Fix issues in insert_cache * Clear the state again * Fix original_storage_at * Early return for empty RLP trie storage * Update comments * Fix borrow_mut issue * Simplify checkpoint_storage_at if branches * Better commenting for gas handling code * Address naming grumbles * More tests * Fix an issue in overwrite_with * Add another test * Fix comment * Remove unnecessary bracket * Move orig to inner if * Remove test coverage for this PR * Add tests for executive original value * Add warn! for an unreachable cause
1 parent 7279d86 commit 710ac6e

File tree

16 files changed

+632
-67
lines changed

16 files changed

+632
-67
lines changed

ethcore/evm/src/interpreter/gasometer.rs

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,17 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
125125
let newval = stack.peek(1);
126126
let val = U256::from(&*ext.storage_at(&address)?);
127127

128-
let gas = if val.is_zero() && !newval.is_zero() {
129-
schedule.sstore_set_gas
128+
let gas = if schedule.eip1283 {
129+
let orig = U256::from(&*ext.initial_storage_at(&address)?);
130+
calculate_eip1283_sstore_gas(schedule, &orig, &val, &newval)
130131
} else {
131-
// Refund for below case is added when actually executing sstore
132-
// !is_zero(&val) && is_zero(newval)
133-
schedule.sstore_reset_gas
132+
if val.is_zero() && !newval.is_zero() {
133+
schedule.sstore_set_gas
134+
} else {
135+
// Refund for below case is added when actually executing sstore
136+
// !is_zero(&val) && is_zero(newval)
137+
schedule.sstore_reset_gas
138+
}
134139
};
135140
Request::Gas(Gas::from(gas))
136141
},
@@ -342,6 +347,89 @@ fn add_gas_usize<Gas: evm::CostType>(value: Gas, num: usize) -> (Gas, bool) {
342347
value.overflow_add(Gas::from(num))
343348
}
344349

350+
#[inline]
351+
fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(schedule: &Schedule, original: &U256, current: &U256, new: &U256) -> Gas {
352+
Gas::from(
353+
if current == new {
354+
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
355+
schedule.sload_gas
356+
} else {
357+
// 2. If current value does not equal new value
358+
if original == current {
359+
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
360+
if original.is_zero() {
361+
// 2.1.1. If original value is 0, 20000 gas is deducted.
362+
schedule.sstore_set_gas
363+
} else {
364+
// 2.1.2. Otherwise, 5000 gas is deducted.
365+
schedule.sstore_reset_gas
366+
367+
// 2.1.2.1. If new value is 0, add 15000 gas to refund counter.
368+
}
369+
} else {
370+
// 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
371+
schedule.sload_gas
372+
373+
// 2.2.1. If original value is not 0
374+
// 2.2.1.1. 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.
375+
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
376+
377+
// 2.2.2. If original value equals new value (this storage slot is reset)
378+
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
379+
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
380+
}
381+
}
382+
)
383+
}
384+
385+
pub fn handle_eip1283_sstore_clears_refund(ext: &mut vm::Ext, original: &U256, current: &U256, new: &U256) {
386+
let sstore_clears_schedule = U256::from(ext.schedule().sstore_refund_gas);
387+
388+
if current == new {
389+
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
390+
} else {
391+
// 2. If current value does not equal new value
392+
if original == current {
393+
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
394+
if original.is_zero() {
395+
// 2.1.1. If original value is 0, 20000 gas is deducted.
396+
} else {
397+
// 2.1.2. Otherwise, 5000 gas is deducted.
398+
if new.is_zero() {
399+
// 2.1.2.1. If new value is 0, add 15000 gas to refund counter.
400+
ext.add_sstore_refund(sstore_clears_schedule);
401+
}
402+
}
403+
} else {
404+
// 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses.
405+
406+
if !original.is_zero() {
407+
// 2.2.1. If original value is not 0
408+
if current.is_zero() {
409+
// 2.2.1.1. 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.
410+
ext.sub_sstore_refund(sstore_clears_schedule);
411+
} else if new.is_zero() {
412+
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
413+
ext.add_sstore_refund(sstore_clears_schedule);
414+
}
415+
}
416+
417+
if original == new {
418+
// 2.2.2. If original value equals new value (this storage slot is reset)
419+
if original.is_zero() {
420+
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
421+
let refund = U256::from(ext.schedule().sstore_set_gas - ext.schedule().sload_gas);
422+
ext.add_sstore_refund(refund);
423+
} else {
424+
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
425+
let refund = U256::from(ext.schedule().sstore_reset_gas - ext.schedule().sload_gas);
426+
ext.add_sstore_refund(refund);
427+
}
428+
}
429+
}
430+
}
431+
}
432+
345433
#[test]
346434
fn test_mem_gas_cost() {
347435
// given

ethcore/evm/src/interpreter/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,8 +515,14 @@ impl<Cost: CostType> Interpreter<Cost> {
515515

516516
let current_val = U256::from(&*ext.storage_at(&address)?);
517517
// Increase refund for clear
518-
if !self.is_zero(&current_val) && self.is_zero(&val) {
519-
ext.inc_sstore_clears();
518+
if ext.schedule().eip1283 {
519+
let original_val = U256::from(&*ext.initial_storage_at(&address)?);
520+
gasometer::handle_eip1283_sstore_clears_refund(ext, &original_val, &current_val, &val);
521+
} else {
522+
if !current_val.is_zero() && val.is_zero() {
523+
let sstore_clears_schedule = U256::from(ext.schedule().sstore_refund_gas);
524+
ext.add_sstore_refund(sstore_clears_schedule);
525+
}
520526
}
521527
ext.set_storage(address, H256::from(&val))?;
522528
},

ethcore/evm/src/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ fn test_jumps(factory: super::Factory) {
716716
test_finalize(vm.exec(params, &mut ext)).unwrap()
717717
};
718718

719-
assert_eq!(ext.sstore_clears, 1);
719+
assert_eq!(ext.sstore_clears, U256::from(ext.schedule().sstore_refund_gas));
720720
assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); // 5!
721721
assert_store(&ext, 1, "0000000000000000000000000000000000000000000000000000000000000078"); // 5!
722722
assert_eq!(gas_left, U256::from(54_117));

ethcore/res/ethereum/constantinople_test.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"eip211Transition": "0x0",
3434
"eip214Transition": "0x0",
3535
"eip155Transition": "0x0",
36-
"eip658Transition": "0x0"
36+
"eip658Transition": "0x0",
37+
"eip1283Transition": "0x0"
3738
},
3839
"genesis": {
3940
"seal": {

ethcore/src/executive.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,9 +560,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
560560
let prev_bal = self.state.balance(&params.address)?;
561561
if let ActionValue::Transfer(val) = params.value {
562562
self.state.sub_balance(&params.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
563-
self.state.new_contract(&params.address, val + prev_bal, nonce_offset);
563+
self.state.new_contract(&params.address, val + prev_bal, nonce_offset)?;
564564
} else {
565-
self.state.new_contract(&params.address, prev_bal, nonce_offset);
565+
self.state.new_contract(&params.address, prev_bal, nonce_offset)?;
566566
}
567567

568568
let trace_info = tracer.prepare_trace_create(&params);
@@ -613,7 +613,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
613613
let schedule = self.machine.schedule(self.info.number);
614614

615615
// refunds from SSTORE nonzero -> zero
616-
let sstore_refunds = U256::from(schedule.sstore_refund_gas) * substate.sstore_clears_count;
616+
let sstore_refunds = substate.sstore_clears_refund;
617617
// refunds from contract suicides
618618
let suicide_refunds = U256::from(schedule.suicide_refund_gas) * U256::from(substate.suicides.len());
619619
let refunds_bound = sstore_refunds + suicide_refunds;
@@ -1587,6 +1587,65 @@ mod tests {
15871587
assert_eq!(state.storage_at(&contract_address, &H256::from(&U256::zero())).unwrap(), H256::from(&U256::from(0)));
15881588
}
15891589

1590+
evm_test!{test_eip1283: test_eip1283_int}
1591+
fn test_eip1283(factory: Factory) {
1592+
let x1 = Address::from(0x1000);
1593+
let x2 = Address::from(0x1001);
1594+
let y1 = Address::from(0x2001);
1595+
let y2 = Address::from(0x2002);
1596+
let operating_address = Address::from(0);
1597+
let k = H256::new();
1598+
1599+
let mut state = get_temp_state_with_factory(factory.clone());
1600+
state.new_contract(&x1, U256::zero(), U256::from(1)).unwrap();
1601+
state.init_code(&x1, "600160005560006000556001600055".from_hex().unwrap()).unwrap();
1602+
state.new_contract(&x2, U256::zero(), U256::from(1)).unwrap();
1603+
state.init_code(&x2, "600060005560016000556000600055".from_hex().unwrap()).unwrap();
1604+
state.new_contract(&y1, U256::zero(), U256::from(1)).unwrap();
1605+
state.init_code(&y1, "600060006000600061100062fffffff4".from_hex().unwrap()).unwrap();
1606+
state.new_contract(&y2, U256::zero(), U256::from(1)).unwrap();
1607+
state.init_code(&y2, "600060006000600061100162fffffff4".from_hex().unwrap()).unwrap();
1608+
1609+
let info = EnvInfo::default();
1610+
let machine = ::ethereum::new_constantinople_test_machine();
1611+
1612+
assert_eq!(state.storage_at(&operating_address, &k).unwrap(), H256::from(U256::from(0)));
1613+
// Test a call via top-level -> y1 -> x1
1614+
let (FinalizationResult { gas_left, .. }, refund, gas) = {
1615+
let gas = U256::from(0xffffffffffu64);
1616+
let mut params = ActionParams::default();
1617+
params.code = Some(Arc::new("6001600055600060006000600061200163fffffffff4".from_hex().unwrap()));
1618+
params.gas = gas;
1619+
let mut substate = Substate::new();
1620+
let mut ex = Executive::new(&mut state, &info, &machine);
1621+
let res = ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap();
1622+
1623+
(res, substate.sstore_clears_refund, gas)
1624+
};
1625+
let gas_used = gas - gas_left;
1626+
// sstore: 0 -> (1) -> () -> (1 -> 0 -> 1)
1627+
assert_eq!(gas_used, U256::from(41860));
1628+
assert_eq!(refund, U256::from(19800));
1629+
1630+
assert_eq!(state.storage_at(&operating_address, &k).unwrap(), H256::from(U256::from(1)));
1631+
// Test a call via top-level -> y2 -> x2
1632+
let (FinalizationResult { gas_left, .. }, refund, gas) = {
1633+
let gas = U256::from(0xffffffffffu64);
1634+
let mut params = ActionParams::default();
1635+
params.code = Some(Arc::new("6001600055600060006000600061200263fffffffff4".from_hex().unwrap()));
1636+
params.gas = gas;
1637+
let mut substate = Substate::new();
1638+
let mut ex = Executive::new(&mut state, &info, &machine);
1639+
let res = ex.call(params, &mut substate, BytesRef::Fixed(&mut []), &mut NoopTracer, &mut NoopVMTracer).unwrap();
1640+
1641+
(res, substate.sstore_clears_refund, gas)
1642+
};
1643+
let gas_used = gas - gas_left;
1644+
// sstore: 1 -> (1) -> () -> (0 -> 1 -> 0)
1645+
assert_eq!(gas_used, U256::from(11860));
1646+
assert_eq!(refund, U256::from(19800));
1647+
}
1648+
15901649
fn wasm_sample_code() -> Arc<Vec<u8>> {
15911650
Arc::new(
15921651
"0061736d01000000010d0360027f7f0060017f0060000002270303656e7603726574000003656e760673656e646572000103656e76066d656d6f727902010110030201020404017000000501000708010463616c6c00020901000ac10101be0102057f017e4100410028020441c0006b22043602042004412c6a41106a220041003602002004412c6a41086a22014200370200200441186a41106a22024100360200200441186a41086a220342003703002004420037022c2004410036021c20044100360218200441186a1001200020022802002202360200200120032903002205370200200441106a2002360200200441086a200537030020042004290318220537022c200420053703002004411410004100200441c0006a3602040b0b0a010041040b0410c00000"

ethcore/src/externalities.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B>
111111
impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
112112
where T: Tracer, V: VMTracer, B: StateBackend
113113
{
114+
fn initial_storage_at(&self, key: &H256) -> vm::Result<H256> {
115+
self.state.checkpoint_storage_at(0, &self.origin_info.address, key).map(|v| v.unwrap_or(H256::zero())).map_err(Into::into)
116+
}
117+
114118
fn storage_at(&self, key: &H256) -> vm::Result<H256> {
115119
self.state.storage_at(&self.origin_info.address, key).map_err(Into::into)
116120
}
@@ -394,8 +398,12 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
394398
self.depth
395399
}
396400

397-
fn inc_sstore_clears(&mut self) {
398-
self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one();
401+
fn add_sstore_refund(&mut self, value: U256) {
402+
self.substate.sstore_clears_refund = self.substate.sstore_clears_refund.saturating_add(value);
403+
}
404+
405+
fn sub_sstore_refund(&mut self, value: U256) {
406+
self.substate.sstore_clears_refund = self.substate.sstore_clears_refund.saturating_sub(value);
399407
}
400408

401409
fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool {

ethcore/src/json_tests/executive.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
111111
self.ext.storage_at(key)
112112
}
113113

114+
fn initial_storage_at(&self, key: &H256) -> vm::Result<H256> {
115+
self.ext.initial_storage_at(key)
116+
}
117+
114118
fn set_storage(&mut self, key: H256, value: H256) -> vm::Result<()> {
115119
self.ext.set_storage(key, value)
116120
}
@@ -205,8 +209,12 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
205209
false
206210
}
207211

208-
fn inc_sstore_clears(&mut self) {
209-
self.ext.inc_sstore_clears()
212+
fn add_sstore_refund(&mut self, value: U256) {
213+
self.ext.add_sstore_refund(value)
214+
}
215+
216+
fn sub_sstore_refund(&mut self, value: U256) {
217+
self.ext.sub_sstore_refund(value)
210218
}
211219
}
212220

ethcore/src/spec/spec.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ pub struct CommonParams {
115115
pub eip145_transition: BlockNumber,
116116
/// Number of first block where EIP-1052 rules begin.
117117
pub eip1052_transition: BlockNumber,
118+
/// Number of first block where EIP-1283 rules begin.
119+
pub eip1283_transition: BlockNumber,
118120
/// Number of first block where EIP-1014 rules begin.
119121
pub eip1014_transition: BlockNumber,
120122
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
@@ -181,6 +183,7 @@ impl CommonParams {
181183
schedule.have_return_data = block_number >= self.eip211_transition;
182184
schedule.have_bitwise_shifting = block_number >= self.eip145_transition;
183185
schedule.have_extcodehash = block_number >= self.eip1052_transition;
186+
schedule.eip1283 = block_number >= self.eip1283_transition;
184187
if block_number >= self.eip210_transition {
185188
schedule.blockhash_gas = 800;
186189
}
@@ -284,6 +287,10 @@ impl From<ethjson::spec::Params> for CommonParams {
284287
BlockNumber::max_value,
285288
Into::into,
286289
),
290+
eip1283_transition: p.eip1283_transition.map_or_else(
291+
BlockNumber::max_value,
292+
Into::into,
293+
),
287294
eip1014_transition: p.eip1014_transition.map_or_else(
288295
BlockNumber::max_value,
289296
Into::into,

0 commit comments

Comments
 (0)