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
This commit is contained in:
parent
6888a968f9
commit
915c366056
@ -125,12 +125,17 @@ impl<Gas: evm::CostType> Gasometer<Gas> {
|
||||
let newval = stack.peek(1);
|
||||
let val = U256::from(&*ext.storage_at(&address)?);
|
||||
|
||||
let gas = if val.is_zero() && !newval.is_zero() {
|
||||
schedule.sstore_set_gas
|
||||
let gas = if schedule.eip1283 {
|
||||
let orig = U256::from(&*ext.initial_storage_at(&address)?);
|
||||
calculate_eip1283_sstore_gas(schedule, &orig, &val, &newval)
|
||||
} else {
|
||||
// Refund for below case is added when actually executing sstore
|
||||
// !is_zero(&val) && is_zero(newval)
|
||||
schedule.sstore_reset_gas
|
||||
if val.is_zero() && !newval.is_zero() {
|
||||
schedule.sstore_set_gas
|
||||
} else {
|
||||
// Refund for below case is added when actually executing sstore
|
||||
// !is_zero(&val) && is_zero(newval)
|
||||
schedule.sstore_reset_gas
|
||||
}
|
||||
};
|
||||
Request::Gas(Gas::from(gas))
|
||||
},
|
||||
@ -342,6 +347,89 @@ fn add_gas_usize<Gas: evm::CostType>(value: Gas, num: usize) -> (Gas, bool) {
|
||||
value.overflow_add(Gas::from(num))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(schedule: &Schedule, original: &U256, current: &U256, new: &U256) -> Gas {
|
||||
Gas::from(
|
||||
if current == new {
|
||||
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
|
||||
schedule.sload_gas
|
||||
} else {
|
||||
// 2. If current value does not equal new value
|
||||
if original == current {
|
||||
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
|
||||
if original.is_zero() {
|
||||
// 2.1.1. If original value is 0, 20000 gas is deducted.
|
||||
schedule.sstore_set_gas
|
||||
} else {
|
||||
// 2.1.2. Otherwise, 5000 gas is deducted.
|
||||
schedule.sstore_reset_gas
|
||||
|
||||
// 2.1.2.1. If new value is 0, add 15000 gas to refund counter.
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
schedule.sload_gas
|
||||
|
||||
// 2.2.1. If original value is not 0
|
||||
// 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.
|
||||
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
|
||||
|
||||
// 2.2.2. If original value equals new value (this storage slot is reset)
|
||||
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
|
||||
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_eip1283_sstore_clears_refund(ext: &mut vm::Ext, original: &U256, current: &U256, new: &U256) {
|
||||
let sstore_clears_schedule = U256::from(ext.schedule().sstore_refund_gas);
|
||||
|
||||
if current == new {
|
||||
// 1. If current value equals new value (this is a no-op), 200 gas is deducted.
|
||||
} else {
|
||||
// 2. If current value does not equal new value
|
||||
if original == current {
|
||||
// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context)
|
||||
if original.is_zero() {
|
||||
// 2.1.1. If original value is 0, 20000 gas is deducted.
|
||||
} else {
|
||||
// 2.1.2. Otherwise, 5000 gas is deducted.
|
||||
if new.is_zero() {
|
||||
// 2.1.2.1. If new value is 0, add 15000 gas to refund counter.
|
||||
ext.add_sstore_refund(sstore_clears_schedule);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 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.
|
||||
|
||||
if !original.is_zero() {
|
||||
// 2.2.1. If original value is not 0
|
||||
if current.is_zero() {
|
||||
// 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.
|
||||
ext.sub_sstore_refund(sstore_clears_schedule);
|
||||
} else if new.is_zero() {
|
||||
// 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter.
|
||||
ext.add_sstore_refund(sstore_clears_schedule);
|
||||
}
|
||||
}
|
||||
|
||||
if original == new {
|
||||
// 2.2.2. If original value equals new value (this storage slot is reset)
|
||||
if original.is_zero() {
|
||||
// 2.2.2.1. If original value is 0, add 19800 gas to refund counter.
|
||||
let refund = U256::from(ext.schedule().sstore_set_gas - ext.schedule().sload_gas);
|
||||
ext.add_sstore_refund(refund);
|
||||
} else {
|
||||
// 2.2.2.2. Otherwise, add 4800 gas to refund counter.
|
||||
let refund = U256::from(ext.schedule().sstore_reset_gas - ext.schedule().sload_gas);
|
||||
ext.add_sstore_refund(refund);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mem_gas_cost() {
|
||||
// given
|
||||
|
@ -629,8 +629,14 @@ impl<Cost: CostType> Interpreter<Cost> {
|
||||
|
||||
let current_val = U256::from(&*ext.storage_at(&address)?);
|
||||
// Increase refund for clear
|
||||
if !current_val.is_zero() && val.is_zero() {
|
||||
ext.inc_sstore_clears();
|
||||
if ext.schedule().eip1283 {
|
||||
let original_val = U256::from(&*ext.initial_storage_at(&address)?);
|
||||
gasometer::handle_eip1283_sstore_clears_refund(ext, &original_val, ¤t_val, &val);
|
||||
} else {
|
||||
if !current_val.is_zero() && val.is_zero() {
|
||||
let sstore_clears_schedule = U256::from(ext.schedule().sstore_refund_gas);
|
||||
ext.add_sstore_refund(sstore_clears_schedule);
|
||||
}
|
||||
}
|
||||
ext.set_storage(address, H256::from(&val))?;
|
||||
},
|
||||
|
@ -716,7 +716,7 @@ fn test_jumps(factory: super::Factory) {
|
||||
test_finalize(vm.exec(&mut ext)).unwrap()
|
||||
};
|
||||
|
||||
assert_eq!(ext.sstore_clears, 1);
|
||||
assert_eq!(ext.sstore_clears, U256::from(ext.schedule().sstore_refund_gas));
|
||||
assert_store(&ext, 0, "0000000000000000000000000000000000000000000000000000000000000000"); // 5!
|
||||
assert_store(&ext, 1, "0000000000000000000000000000000000000000000000000000000000000078"); // 5!
|
||||
assert_eq!(gas_left, U256::from(54_117));
|
||||
|
@ -33,7 +33,8 @@
|
||||
"eip211Transition": "0x0",
|
||||
"eip214Transition": "0x0",
|
||||
"eip155Transition": "0x0",
|
||||
"eip658Transition": "0x0"
|
||||
"eip658Transition": "0x0",
|
||||
"eip1283Transition": "0x0"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
|
@ -574,9 +574,9 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
|
||||
let prev_bal = self.state.balance(¶ms.address)?;
|
||||
if let ActionValue::Transfer(val) = params.value {
|
||||
self.state.sub_balance(¶ms.sender, &val, &mut substate.to_cleanup_mode(&schedule))?;
|
||||
self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset);
|
||||
self.state.new_contract(¶ms.address, val + prev_bal, nonce_offset)?;
|
||||
} else {
|
||||
self.state.new_contract(¶ms.address, prev_bal, nonce_offset);
|
||||
self.state.new_contract(¶ms.address, prev_bal, nonce_offset)?;
|
||||
}
|
||||
|
||||
let trace_info = tracer.prepare_trace_create(¶ms);
|
||||
@ -627,7 +627,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
|
||||
let schedule = self.schedule;
|
||||
|
||||
// refunds from SSTORE nonzero -> zero
|
||||
let sstore_refunds = U256::from(schedule.sstore_refund_gas) * substate.sstore_clears_count;
|
||||
let sstore_refunds = substate.sstore_clears_refund;
|
||||
// refunds from contract suicides
|
||||
let suicide_refunds = U256::from(schedule.suicide_refund_gas) * U256::from(substate.suicides.len());
|
||||
let refunds_bound = sstore_refunds + suicide_refunds;
|
||||
@ -1614,6 +1614,66 @@ mod tests {
|
||||
assert_eq!(state.storage_at(&contract_address, &H256::from(&U256::zero())).unwrap(), H256::from(&U256::from(0)));
|
||||
}
|
||||
|
||||
evm_test!{test_eip1283: test_eip1283_int}
|
||||
fn test_eip1283(factory: Factory) {
|
||||
let x1 = Address::from(0x1000);
|
||||
let x2 = Address::from(0x1001);
|
||||
let y1 = Address::from(0x2001);
|
||||
let y2 = Address::from(0x2002);
|
||||
let operating_address = Address::from(0);
|
||||
let k = H256::new();
|
||||
|
||||
let mut state = get_temp_state_with_factory(factory.clone());
|
||||
state.new_contract(&x1, U256::zero(), U256::from(1)).unwrap();
|
||||
state.init_code(&x1, "600160005560006000556001600055".from_hex().unwrap()).unwrap();
|
||||
state.new_contract(&x2, U256::zero(), U256::from(1)).unwrap();
|
||||
state.init_code(&x2, "600060005560016000556000600055".from_hex().unwrap()).unwrap();
|
||||
state.new_contract(&y1, U256::zero(), U256::from(1)).unwrap();
|
||||
state.init_code(&y1, "600060006000600061100062fffffff4".from_hex().unwrap()).unwrap();
|
||||
state.new_contract(&y2, U256::zero(), U256::from(1)).unwrap();
|
||||
state.init_code(&y2, "600060006000600061100162fffffff4".from_hex().unwrap()).unwrap();
|
||||
|
||||
let info = EnvInfo::default();
|
||||
let machine = ::ethereum::new_constantinople_test_machine();
|
||||
let schedule = machine.schedule(info.number);
|
||||
|
||||
assert_eq!(state.storage_at(&operating_address, &k).unwrap(), H256::from(U256::from(0)));
|
||||
// Test a call via top-level -> y1 -> x1
|
||||
let (FinalizationResult { gas_left, .. }, refund, gas) = {
|
||||
let gas = U256::from(0xffffffffffu64);
|
||||
let mut params = ActionParams::default();
|
||||
params.code = Some(Arc::new("6001600055600060006000600061200163fffffffff4".from_hex().unwrap()));
|
||||
params.gas = gas;
|
||||
let mut substate = Substate::new();
|
||||
let mut ex = Executive::new(&mut state, &info, &machine, &schedule);
|
||||
let res = ex.call(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap();
|
||||
|
||||
(res, substate.sstore_clears_refund, gas)
|
||||
};
|
||||
let gas_used = gas - gas_left;
|
||||
// sstore: 0 -> (1) -> () -> (1 -> 0 -> 1)
|
||||
assert_eq!(gas_used, U256::from(41860));
|
||||
assert_eq!(refund, U256::from(19800));
|
||||
|
||||
assert_eq!(state.storage_at(&operating_address, &k).unwrap(), H256::from(U256::from(1)));
|
||||
// Test a call via top-level -> y2 -> x2
|
||||
let (FinalizationResult { gas_left, .. }, refund, gas) = {
|
||||
let gas = U256::from(0xffffffffffu64);
|
||||
let mut params = ActionParams::default();
|
||||
params.code = Some(Arc::new("6001600055600060006000600061200263fffffffff4".from_hex().unwrap()));
|
||||
params.gas = gas;
|
||||
let mut substate = Substate::new();
|
||||
let mut ex = Executive::new(&mut state, &info, &machine, &schedule);
|
||||
let res = ex.call(params, &mut substate, &mut NoopTracer, &mut NoopVMTracer).unwrap();
|
||||
|
||||
(res, substate.sstore_clears_refund, gas)
|
||||
};
|
||||
let gas_used = gas - gas_left;
|
||||
// sstore: 1 -> (1) -> () -> (0 -> 1 -> 0)
|
||||
assert_eq!(gas_used, U256::from(11860));
|
||||
assert_eq!(refund, U256::from(19800));
|
||||
}
|
||||
|
||||
fn wasm_sample_code() -> Arc<Vec<u8>> {
|
||||
Arc::new(
|
||||
"0061736d01000000010d0360027f7f0060017f0060000002270303656e7603726574000003656e760673656e646572000103656e76066d656d6f727902010110030201020404017000000501000708010463616c6c00020901000ac10101be0102057f017e4100410028020441c0006b22043602042004412c6a41106a220041003602002004412c6a41086a22014200370200200441186a41106a22024100360200200441186a41086a220342003703002004420037022c2004410036021c20044100360218200441186a1001200020022802002202360200200120032903002205370200200441106a2002360200200441086a200537030020042004290318220537022c200420053703002004411410004100200441c0006a3602040b0b0a010041040b0410c00000"
|
||||
|
@ -113,6 +113,10 @@ impl<'a, T: 'a, V: 'a, B: 'a> Externalities<'a, T, V, B>
|
||||
impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
|
||||
where T: Tracer, V: VMTracer, B: StateBackend
|
||||
{
|
||||
fn initial_storage_at(&self, key: &H256) -> vm::Result<H256> {
|
||||
self.state.checkpoint_storage_at(0, &self.origin_info.address, key).map(|v| v.unwrap_or(H256::zero())).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn storage_at(&self, key: &H256) -> vm::Result<H256> {
|
||||
self.state.storage_at(&self.origin_info.address, key).map_err(Into::into)
|
||||
}
|
||||
@ -390,8 +394,12 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for Externalities<'a, T, V, B>
|
||||
self.depth
|
||||
}
|
||||
|
||||
fn inc_sstore_clears(&mut self) {
|
||||
self.substate.sstore_clears_count = self.substate.sstore_clears_count + U256::one();
|
||||
fn add_sstore_refund(&mut self, value: U256) {
|
||||
self.substate.sstore_clears_refund = self.substate.sstore_clears_refund.saturating_add(value);
|
||||
}
|
||||
|
||||
fn sub_sstore_refund(&mut self, value: U256) {
|
||||
self.substate.sstore_clears_refund = self.substate.sstore_clears_refund.saturating_sub(value);
|
||||
}
|
||||
|
||||
fn trace_next_instruction(&mut self, pc: usize, instruction: u8, current_gas: U256) -> bool {
|
||||
|
@ -112,6 +112,10 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
|
||||
self.ext.storage_at(key)
|
||||
}
|
||||
|
||||
fn initial_storage_at(&self, key: &H256) -> vm::Result<H256> {
|
||||
self.ext.initial_storage_at(key)
|
||||
}
|
||||
|
||||
fn set_storage(&mut self, key: H256, value: H256) -> vm::Result<()> {
|
||||
self.ext.set_storage(key, value)
|
||||
}
|
||||
@ -205,8 +209,12 @@ impl<'a, T: 'a, V: 'a, B: 'a> Ext for TestExt<'a, T, V, B>
|
||||
false
|
||||
}
|
||||
|
||||
fn inc_sstore_clears(&mut self) {
|
||||
self.ext.inc_sstore_clears()
|
||||
fn add_sstore_refund(&mut self, value: U256) {
|
||||
self.ext.add_sstore_refund(value)
|
||||
}
|
||||
|
||||
fn sub_sstore_refund(&mut self, value: U256) {
|
||||
self.ext.sub_sstore_refund(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,8 @@ pub struct CommonParams {
|
||||
pub eip145_transition: BlockNumber,
|
||||
/// Number of first block where EIP-1052 rules begin.
|
||||
pub eip1052_transition: BlockNumber,
|
||||
/// Number of first block where EIP-1283 rules begin.
|
||||
pub eip1283_transition: BlockNumber,
|
||||
/// Number of first block where EIP-1014 rules begin.
|
||||
pub eip1014_transition: BlockNumber,
|
||||
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
|
||||
@ -183,6 +185,7 @@ impl CommonParams {
|
||||
schedule.have_return_data = block_number >= self.eip211_transition;
|
||||
schedule.have_bitwise_shifting = block_number >= self.eip145_transition;
|
||||
schedule.have_extcodehash = block_number >= self.eip1052_transition;
|
||||
schedule.eip1283 = block_number >= self.eip1283_transition;
|
||||
if block_number >= self.eip210_transition {
|
||||
schedule.blockhash_gas = 800;
|
||||
}
|
||||
@ -286,6 +289,10 @@ impl From<ethjson::spec::Params> for CommonParams {
|
||||
BlockNumber::max_value,
|
||||
Into::into,
|
||||
),
|
||||
eip1283_transition: p.eip1283_transition.map_or_else(
|
||||
BlockNumber::max_value,
|
||||
Into::into,
|
||||
),
|
||||
eip1014_transition: p.eip1014_transition.map_or_else(
|
||||
BlockNumber::max_value,
|
||||
Into::into,
|
||||
|
@ -59,6 +59,10 @@ pub struct Account {
|
||||
// LRU Cache of the trie-backed storage.
|
||||
// This is limited to `STORAGE_CACHE_ITEMS` recent queries
|
||||
storage_cache: RefCell<LruCache<H256, H256>>,
|
||||
// LRU Cache of the trie-backed storage for original value.
|
||||
// This is only used when the initial storage root is different compared to
|
||||
// what is in the database. That is, it is only used for new contracts.
|
||||
original_storage_cache: Option<(H256, RefCell<LruCache<H256, H256>>)>,
|
||||
// Modified storage. Accumulates changes to storage made in `set_storage`
|
||||
// Takes precedence over `storage_cache`.
|
||||
storage_changes: HashMap<H256, H256>,
|
||||
@ -81,6 +85,7 @@ impl From<BasicAccount> for Account {
|
||||
nonce: basic.nonce,
|
||||
storage_root: basic.storage_root,
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: None,
|
||||
storage_changes: HashMap::new(),
|
||||
code_hash: basic.code_hash,
|
||||
code_size: None,
|
||||
@ -100,6 +105,7 @@ impl Account {
|
||||
nonce: nonce,
|
||||
storage_root: KECCAK_NULL_RLP,
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: None,
|
||||
storage_changes: storage,
|
||||
code_hash: keccak(&code),
|
||||
code_size: Some(code.len()),
|
||||
@ -120,6 +126,7 @@ impl Account {
|
||||
nonce: pod.nonce,
|
||||
storage_root: KECCAK_NULL_RLP,
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: None,
|
||||
storage_changes: pod.storage.into_iter().collect(),
|
||||
code_hash: pod.code.as_ref().map_or(KECCAK_EMPTY, |c| keccak(c)),
|
||||
code_filth: Filth::Dirty,
|
||||
@ -136,6 +143,7 @@ impl Account {
|
||||
nonce: nonce,
|
||||
storage_root: KECCAK_NULL_RLP,
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: None,
|
||||
storage_changes: HashMap::new(),
|
||||
code_hash: KECCAK_EMPTY,
|
||||
code_cache: Arc::new(vec![]),
|
||||
@ -154,12 +162,17 @@ impl Account {
|
||||
|
||||
/// Create a new contract account.
|
||||
/// NOTE: make sure you use `init_code` on this before `commit`ing.
|
||||
pub fn new_contract(balance: U256, nonce: U256) -> Account {
|
||||
pub fn new_contract(balance: U256, nonce: U256, original_storage_root: H256) -> Account {
|
||||
Account {
|
||||
balance: balance,
|
||||
nonce: nonce,
|
||||
storage_root: KECCAK_NULL_RLP,
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: if original_storage_root == KECCAK_NULL_RLP {
|
||||
None
|
||||
} else {
|
||||
Some((original_storage_root, Self::empty_storage_cache()))
|
||||
},
|
||||
storage_changes: HashMap::new(),
|
||||
code_hash: KECCAK_EMPTY,
|
||||
code_cache: Arc::new(vec![]),
|
||||
@ -204,11 +217,43 @@ impl Account {
|
||||
if let Some(value) = self.cached_storage_at(key) {
|
||||
return Ok(value);
|
||||
}
|
||||
let db = SecTrieDB::new(db, &self.storage_root)?;
|
||||
Self::get_and_cache_storage(
|
||||
&self.storage_root,
|
||||
&mut self.storage_cache.borrow_mut(),
|
||||
db,
|
||||
key)
|
||||
}
|
||||
|
||||
/// Get (and cache) the contents of the trie's storage at `key`.
|
||||
/// Does not take modified storage into account.
|
||||
pub fn original_storage_at(&self, db: &HashDB<KeccakHasher>, key: &H256) -> TrieResult<H256> {
|
||||
if let Some(value) = self.cached_original_storage_at(key) {
|
||||
return Ok(value);
|
||||
}
|
||||
match &self.original_storage_cache {
|
||||
Some((ref original_storage_root, ref original_storage_cache)) =>
|
||||
Self::get_and_cache_storage(
|
||||
original_storage_root,
|
||||
&mut original_storage_cache.borrow_mut(),
|
||||
db,
|
||||
key
|
||||
),
|
||||
None =>
|
||||
Self::get_and_cache_storage(
|
||||
&self.storage_root,
|
||||
&mut self.storage_cache.borrow_mut(),
|
||||
db,
|
||||
key
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_and_cache_storage(storage_root: &H256, storage_cache: &mut LruCache<H256, H256>, db: &HashDB<KeccakHasher>, key: &H256) -> TrieResult<H256> {
|
||||
let db = SecTrieDB::new(db, storage_root)?;
|
||||
let panicky_decoder = |bytes:&[u8]| ::rlp::decode(&bytes).expect("decoding db value failed");
|
||||
let item: U256 = db.get_with(key, panicky_decoder)?.unwrap_or_else(U256::zero);
|
||||
let value: H256 = item.into();
|
||||
self.storage_cache.borrow_mut().insert(key.clone(), value.clone());
|
||||
storage_cache.insert(key.clone(), value.clone());
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@ -218,10 +263,38 @@ impl Account {
|
||||
if let Some(value) = self.storage_changes.get(key) {
|
||||
return Some(value.clone())
|
||||
}
|
||||
if let Some(value) = self.storage_cache.borrow_mut().get_mut(key) {
|
||||
return Some(value.clone())
|
||||
self.cached_moved_original_storage_at(key)
|
||||
}
|
||||
|
||||
/// Get cached original storage value after last state commitment. Returns `None` if the key is not in the cache.
|
||||
pub fn cached_original_storage_at(&self, key: &H256) -> Option<H256> {
|
||||
match &self.original_storage_cache {
|
||||
Some((_, ref original_storage_cache)) => {
|
||||
if let Some(value) = original_storage_cache.borrow_mut().get_mut(key) {
|
||||
Some(value.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
None => {
|
||||
self.cached_moved_original_storage_at(key)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cached original storage value since last contract creation on this address. Returns `None` if the key is not in the cache.
|
||||
fn cached_moved_original_storage_at(&self, key: &H256) -> Option<H256> {
|
||||
// If storage root is empty RLP, then early return zero value. Practically, this makes it so that if
|
||||
// `original_storage_cache` is used, then `storage_cache` will always remain empty.
|
||||
if self.storage_root == KECCAK_NULL_RLP {
|
||||
return Some(H256::new());
|
||||
}
|
||||
|
||||
if let Some(value) = self.storage_cache.borrow_mut().get_mut(key) {
|
||||
Some(value.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// return the balance associated with this account.
|
||||
@ -357,7 +430,27 @@ impl Account {
|
||||
}
|
||||
|
||||
/// Return the storage root associated with this account or None if it has been altered via the overlay.
|
||||
pub fn storage_root(&self) -> Option<&H256> { if self.storage_is_clean() {Some(&self.storage_root)} else {None} }
|
||||
pub fn storage_root(&self) -> Option<H256> {
|
||||
if self.storage_is_clean() {
|
||||
Some(self.storage_root)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the original storage root of this account.
|
||||
pub fn original_storage_root(&self) -> H256 {
|
||||
if let Some((original_storage_root, _)) = self.original_storage_cache {
|
||||
original_storage_root
|
||||
} else {
|
||||
self.storage_root
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage root where the account changes are based upon.
|
||||
pub fn base_storage_root(&self) -> H256 {
|
||||
self.storage_root
|
||||
}
|
||||
|
||||
/// Return the storage overlay.
|
||||
pub fn storage_changes(&self) -> &HashMap<H256, H256> { &self.storage_changes }
|
||||
@ -392,6 +485,7 @@ impl Account {
|
||||
|
||||
self.storage_cache.borrow_mut().insert(k, v);
|
||||
}
|
||||
self.original_storage_cache = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -429,6 +523,7 @@ impl Account {
|
||||
nonce: self.nonce.clone(),
|
||||
storage_root: self.storage_root.clone(),
|
||||
storage_cache: Self::empty_storage_cache(),
|
||||
original_storage_cache: self.original_storage_cache.as_ref().map(|(r, _)| (*r, Self::empty_storage_cache())),
|
||||
storage_changes: HashMap::new(),
|
||||
code_hash: self.code_hash.clone(),
|
||||
code_size: self.code_size.clone(),
|
||||
@ -449,6 +544,7 @@ impl Account {
|
||||
pub fn clone_all(&self) -> Account {
|
||||
let mut account = self.clone_dirty();
|
||||
account.storage_cache = self.storage_cache.clone();
|
||||
account.original_storage_cache = self.original_storage_cache.clone();
|
||||
account
|
||||
}
|
||||
|
||||
@ -458,16 +554,21 @@ impl Account {
|
||||
pub fn overwrite_with(&mut self, other: Account) {
|
||||
self.balance = other.balance;
|
||||
self.nonce = other.nonce;
|
||||
self.storage_root = other.storage_root;
|
||||
self.code_hash = other.code_hash;
|
||||
self.code_filth = other.code_filth;
|
||||
self.code_cache = other.code_cache;
|
||||
self.code_size = other.code_size;
|
||||
self.address_hash = other.address_hash;
|
||||
let mut cache = self.storage_cache.borrow_mut();
|
||||
for (k, v) in other.storage_cache.into_inner() {
|
||||
cache.insert(k, v);
|
||||
if self.storage_root == other.storage_root {
|
||||
let mut cache = self.storage_cache.borrow_mut();
|
||||
for (k, v) in other.storage_cache.into_inner() {
|
||||
cache.insert(k, v);
|
||||
}
|
||||
} else {
|
||||
self.storage_cache = other.storage_cache;
|
||||
}
|
||||
self.original_storage_cache = other.original_storage_cache;
|
||||
self.storage_root = other.storage_root;
|
||||
self.storage_changes = other.storage_changes;
|
||||
}
|
||||
}
|
||||
@ -526,7 +627,7 @@ mod tests {
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
let rlp = {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
a.set_storage(0x00u64.into(), 0x1234u64.into());
|
||||
a.commit_storage(&Default::default(), &mut db).unwrap();
|
||||
a.init_code(vec![]);
|
||||
@ -535,7 +636,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let a = Account::from_rlp(&rlp).expect("decoding db value failed");
|
||||
assert_eq!(*a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
assert_eq!(a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
assert_eq!(a.storage_at(&db.immutable(), &0x00u64.into()).unwrap(), 0x1234u64.into());
|
||||
assert_eq!(a.storage_at(&db.immutable(), &0x01u64.into()).unwrap(), H256::default());
|
||||
}
|
||||
@ -546,7 +647,7 @@ mod tests {
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
|
||||
let rlp = {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
||||
a.commit_code(&mut db);
|
||||
a.rlp()
|
||||
@ -561,18 +662,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn commit_storage() {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
a.set_storage(0.into(), 0x1234.into());
|
||||
assert_eq!(a.storage_root(), None);
|
||||
a.commit_storage(&Default::default(), &mut db).unwrap();
|
||||
assert_eq!(*a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
assert_eq!(a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_remove_commit_storage() {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
a.set_storage(0.into(), 0x1234.into());
|
||||
@ -581,12 +682,12 @@ mod tests {
|
||||
a.commit_storage(&Default::default(), &mut db).unwrap();
|
||||
a.set_storage(1.into(), 0.into());
|
||||
a.commit_storage(&Default::default(), &mut db).unwrap();
|
||||
assert_eq!(*a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
assert_eq!(a.storage_root().unwrap(), "c57e1afb758b07f8d2c8f13a3b6e44fa5ff94ab266facc5a4fd3f062426e50b2".into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_code() {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
||||
@ -598,7 +699,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reset_code() {
|
||||
let mut a = Account::new_contract(69.into(), 0.into());
|
||||
let mut a = Account::new_contract(69.into(), 0.into(), KECCAK_NULL_RLP);
|
||||
let mut db = MemoryDB::new();
|
||||
let mut db = AccountDBMut::new(&mut db, &Address::new());
|
||||
a.init_code(vec![0x55, 0x44, 0xffu8]);
|
||||
@ -629,7 +730,7 @@ mod tests {
|
||||
assert_eq!(*a.balance(), 69u8.into());
|
||||
assert_eq!(*a.nonce(), 0u8.into());
|
||||
assert_eq!(a.code_hash(), KECCAK_EMPTY);
|
||||
assert_eq!(a.storage_root().unwrap(), &KECCAK_NULL_RLP);
|
||||
assert_eq!(a.storage_root().unwrap(), KECCAK_NULL_RLP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -181,6 +181,8 @@ impl AccountEntry {
|
||||
Some(acc) => {
|
||||
if let Some(ref mut ours) = self.account {
|
||||
ours.overwrite_with(acc);
|
||||
} else {
|
||||
self.account = Some(acc);
|
||||
}
|
||||
},
|
||||
None => self.account = None,
|
||||
@ -401,9 +403,12 @@ impl<B: Backend> State<B> {
|
||||
self.factories.vm.clone()
|
||||
}
|
||||
|
||||
/// Create a recoverable checkpoint of this state.
|
||||
pub fn checkpoint(&mut self) {
|
||||
self.checkpoints.get_mut().push(HashMap::new());
|
||||
/// Create a recoverable checkpoint of this state. Return the checkpoint index.
|
||||
pub fn checkpoint(&mut self) -> usize {
|
||||
let checkpoints = self.checkpoints.get_mut();
|
||||
let index = checkpoints.len();
|
||||
checkpoints.push(HashMap::new());
|
||||
index
|
||||
}
|
||||
|
||||
/// Merge last checkpoint with previous.
|
||||
@ -416,7 +421,16 @@ impl<B: Backend> State<B> {
|
||||
**prev = checkpoint;
|
||||
} else {
|
||||
for (k, v) in checkpoint.drain() {
|
||||
prev.entry(k).or_insert(v);
|
||||
match prev.entry(k) {
|
||||
Entry::Occupied(mut e) => {
|
||||
if e.get().is_none() {
|
||||
e.insert(v);
|
||||
}
|
||||
},
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -494,8 +508,10 @@ impl<B: Backend> State<B> {
|
||||
|
||||
/// Create a new contract at address `contract`. If there is already an account at the address
|
||||
/// it will have its code reset, ready for `init_code()`.
|
||||
pub fn new_contract(&mut self, contract: &Address, balance: U256, nonce_offset: U256) {
|
||||
self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce + nonce_offset))));
|
||||
pub fn new_contract(&mut self, contract: &Address, balance: U256, nonce_offset: U256) -> TrieResult<()> {
|
||||
let original_storage_root = self.original_storage_root(contract)?;
|
||||
self.insert_cache(contract, AccountEntry::new_dirty(Some(Account::new_contract(balance, self.account_start_nonce + nonce_offset, original_storage_root))));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an existing account.
|
||||
@ -536,11 +552,84 @@ impl<B: Backend> State<B> {
|
||||
/// Get the storage root of account `a`.
|
||||
pub fn storage_root(&self, a: &Address) -> TrieResult<Option<H256>> {
|
||||
self.ensure_cached(a, RequireCache::None, true,
|
||||
|a| a.as_ref().and_then(|account| account.storage_root().cloned()))
|
||||
|a| a.as_ref().and_then(|account| account.storage_root()))
|
||||
}
|
||||
|
||||
/// Mutate storage of account `address` so that it is `value` for `key`.
|
||||
pub fn storage_at(&self, address: &Address, key: &H256) -> TrieResult<H256> {
|
||||
/// Get the original storage root since last commit of account `a`.
|
||||
pub fn original_storage_root(&self, a: &Address) -> TrieResult<H256> {
|
||||
Ok(self.ensure_cached(a, RequireCache::None, true,
|
||||
|a| a.as_ref().map(|account| account.original_storage_root()))?
|
||||
.unwrap_or(KECCAK_NULL_RLP))
|
||||
}
|
||||
|
||||
/// Get the value of storage at a specific checkpoint.
|
||||
pub fn checkpoint_storage_at(&self, checkpoint_index: usize, address: &Address, key: &H256) -> TrieResult<Option<H256>> {
|
||||
enum ReturnKind {
|
||||
/// Use original storage at value at this address.
|
||||
OriginalAt,
|
||||
/// Use the downward checkpoint value.
|
||||
Downward,
|
||||
}
|
||||
|
||||
let (checkpoints_len, kind) = {
|
||||
let checkpoints = self.checkpoints.borrow();
|
||||
let checkpoints_len = checkpoints.len();
|
||||
let checkpoint = match checkpoints.get(checkpoint_index) {
|
||||
Some(checkpoint) => checkpoint,
|
||||
// The checkpoint was not found. Return None.
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let kind = match checkpoint.get(address) {
|
||||
// The account exists at this checkpoint.
|
||||
Some(Some(AccountEntry { account: Some(ref account), .. })) => {
|
||||
if let Some(value) = account.cached_storage_at(key) {
|
||||
return Ok(Some(value));
|
||||
} else {
|
||||
// This account has checkpoint entry, but the key is not in the entry's cache. We can use
|
||||
// original_storage_at if current account's original storage root is the same as checkpoint
|
||||
// account's original storage root. Otherwise, the account must be a newly created contract.
|
||||
if account.base_storage_root() == self.original_storage_root(address)? {
|
||||
ReturnKind::OriginalAt
|
||||
} else {
|
||||
// If account base storage root is different from the original storage root since last
|
||||
// commit, then it can only be created from a new contract, where the base storage root
|
||||
// would always be empty. Note that this branch is actually never called, because
|
||||
// `cached_storage_at` handled this case.
|
||||
warn!("Trying to get an account's cached storage value, but base storage root does not equal to original storage root! Assuming the value is empty.");
|
||||
return Ok(Some(H256::new()));
|
||||
}
|
||||
}
|
||||
},
|
||||
// The account didn't exist at that point. Return empty value.
|
||||
Some(Some(AccountEntry { account: None, .. })) => return Ok(Some(H256::new())),
|
||||
// The value was not cached at that checkpoint, meaning it was not modified at all.
|
||||
Some(None) => ReturnKind::OriginalAt,
|
||||
// This key does not have a checkpoint entry.
|
||||
None => ReturnKind::Downward,
|
||||
};
|
||||
|
||||
(checkpoints_len, kind)
|
||||
};
|
||||
|
||||
match kind {
|
||||
ReturnKind::Downward => {
|
||||
if checkpoint_index >= checkpoints_len - 1 {
|
||||
Ok(Some(self.storage_at(address, key)?))
|
||||
} else {
|
||||
self.checkpoint_storage_at(checkpoint_index + 1, address, key)
|
||||
}
|
||||
},
|
||||
ReturnKind::OriginalAt => Ok(Some(self.original_storage_at(address, key)?)),
|
||||
}
|
||||
}
|
||||
|
||||
fn storage_at_inner<FCachedStorageAt, FStorageAt>(
|
||||
&self, address: &Address, key: &H256, f_cached_at: FCachedStorageAt, f_at: FStorageAt,
|
||||
) -> TrieResult<H256> where
|
||||
FCachedStorageAt: Fn(&Account, &H256) -> Option<H256>,
|
||||
FStorageAt: Fn(&Account, &HashDB<KeccakHasher>, &H256) -> TrieResult<H256>
|
||||
{
|
||||
// Storage key search and update works like this:
|
||||
// 1. If there's an entry for the account in the local cache check for the key and return it if found.
|
||||
// 2. If there's an entry for the account in the global cache check for the key or load it into that account.
|
||||
@ -553,7 +642,7 @@ impl<B: Backend> State<B> {
|
||||
if let Some(maybe_acc) = local_cache.get(address) {
|
||||
match maybe_acc.account {
|
||||
Some(ref account) => {
|
||||
if let Some(value) = account.cached_storage_at(key) {
|
||||
if let Some(value) = f_cached_at(account, key) {
|
||||
return Ok(value);
|
||||
} else {
|
||||
local_account = Some(maybe_acc);
|
||||
@ -567,7 +656,7 @@ impl<B: Backend> State<B> {
|
||||
None => Ok(H256::new()),
|
||||
Some(a) => {
|
||||
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address));
|
||||
a.storage_at(account_db.as_hashdb(), key)
|
||||
f_at(a, account_db.as_hashdb(), key)
|
||||
}
|
||||
});
|
||||
|
||||
@ -579,7 +668,7 @@ impl<B: Backend> State<B> {
|
||||
if let Some(ref mut acc) = local_account {
|
||||
if let Some(ref account) = acc.account {
|
||||
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(address));
|
||||
return account.storage_at(account_db.as_hashdb(), key)
|
||||
return f_at(account, account_db.as_hashdb(), key)
|
||||
} else {
|
||||
return Ok(H256::new())
|
||||
}
|
||||
@ -595,12 +684,32 @@ impl<B: Backend> State<B> {
|
||||
let maybe_acc = db.get_with(address, from_rlp)?;
|
||||
let r = maybe_acc.as_ref().map_or(Ok(H256::new()), |a| {
|
||||
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), a.address_hash(address));
|
||||
a.storage_at(account_db.as_hashdb(), key)
|
||||
f_at(a, account_db.as_hashdb(), key)
|
||||
});
|
||||
self.insert_cache(address, AccountEntry::new_clean(maybe_acc));
|
||||
r
|
||||
}
|
||||
|
||||
/// Mutate storage of account `address` so that it is `value` for `key`.
|
||||
pub fn storage_at(&self, address: &Address, key: &H256) -> TrieResult<H256> {
|
||||
self.storage_at_inner(
|
||||
address,
|
||||
key,
|
||||
|account, key| { account.cached_storage_at(key) },
|
||||
|account, db, key| { account.storage_at(db, key) },
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the value of storage after last state commitment.
|
||||
pub fn original_storage_at(&self, address: &Address, key: &H256) -> TrieResult<H256> {
|
||||
self.storage_at_inner(
|
||||
address,
|
||||
key,
|
||||
|account, key| { account.cached_original_storage_at(key) },
|
||||
|account, db, key| { account.original_storage_at(db, key) },
|
||||
)
|
||||
}
|
||||
|
||||
/// Get accounts' code.
|
||||
pub fn code(&self, a: &Address) -> TrieResult<Option<Arc<Bytes>>> {
|
||||
self.ensure_cached(a, RequireCache::Code, true,
|
||||
@ -671,13 +780,13 @@ impl<B: Backend> State<B> {
|
||||
/// Initialise the code of account `a` so that it is `code`.
|
||||
/// NOTE: Account should have been created with `new_contract`.
|
||||
pub fn init_code(&mut self, a: &Address, code: Bytes) -> TrieResult<()> {
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{})?.init_code(code);
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, KECCAK_NULL_RLP), |_| {})?.init_code(code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset the code of account `a` so that it is `code`.
|
||||
pub fn reset_code(&mut self, a: &Address, code: Bytes) -> TrieResult<()> {
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce), |_|{})?.reset_code(code);
|
||||
self.require_or_from(a, true, || Account::new_contract(0.into(), self.account_start_nonce, KECCAK_NULL_RLP), |_| {})?.reset_code(code);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -761,6 +870,7 @@ impl<B: Backend> State<B> {
|
||||
|
||||
/// Commits our cached account changes into the trie.
|
||||
pub fn commit(&mut self) -> Result<(), Error> {
|
||||
assert!(self.checkpoints.borrow().is_empty());
|
||||
// first, commit the sub trees.
|
||||
let mut accounts = self.cache.borrow_mut();
|
||||
for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) {
|
||||
@ -806,6 +916,7 @@ impl<B: Backend> State<B> {
|
||||
|
||||
/// Clear state cache
|
||||
pub fn clear(&mut self) {
|
||||
assert!(self.checkpoints.borrow().is_empty());
|
||||
self.cache.borrow_mut().clear();
|
||||
}
|
||||
|
||||
@ -1000,7 +1111,7 @@ impl<B: Backend> State<B> {
|
||||
|
||||
/// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too.
|
||||
fn require<'a>(&'a self, a: &Address, require_code: bool) -> TrieResult<RefMut<'a, Account>> {
|
||||
self.require_or_from(a, require_code, || Account::new_basic(0u8.into(), self.account_start_nonce), |_|{})
|
||||
self.require_or_from(a, require_code, || Account::new_basic(0u8.into(), self.account_start_nonce), |_| {})
|
||||
}
|
||||
|
||||
/// Pull account `a` in our cache from the trie DB. `require_code` requires that the code be cached, too.
|
||||
@ -1140,7 +1251,7 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
use std::str::FromStr;
|
||||
use rustc_hex::FromHex;
|
||||
use hash::keccak;
|
||||
use hash::{keccak, KECCAK_NULL_RLP};
|
||||
use super::*;
|
||||
use ethkey::Secret;
|
||||
use ethereum_types::{H256, U256, Address};
|
||||
@ -1992,7 +2103,7 @@ mod tests {
|
||||
let a = Address::zero();
|
||||
let (root, db) = {
|
||||
let mut state = get_temp_state();
|
||||
state.require_or_from(&a, false, ||Account::new_contract(42.into(), 0.into()), |_|{}).unwrap();
|
||||
state.require_or_from(&a, false, || Account::new_contract(42.into(), 0.into(), KECCAK_NULL_RLP), |_|{}).unwrap();
|
||||
state.init_code(&a, vec![1, 2, 3]).unwrap();
|
||||
assert_eq!(state.code(&a).unwrap(), Some(Arc::new(vec![1u8, 2, 3])));
|
||||
state.commit().unwrap();
|
||||
@ -2196,6 +2307,161 @@ mod tests {
|
||||
assert_eq!(state.balance(&a).unwrap(), U256::from(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkpoint_revert_to_get_storage_at() {
|
||||
let mut state = get_temp_state();
|
||||
let a = Address::zero();
|
||||
let k = H256::from(U256::from(0));
|
||||
|
||||
let c0 = state.checkpoint();
|
||||
let c1 = state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(1))).unwrap();
|
||||
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(1)));
|
||||
|
||||
state.revert_to_checkpoint(); // Revert to c1.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkpoint_from_empty_get_storage_at() {
|
||||
let mut state = get_temp_state();
|
||||
let a = Address::zero();
|
||||
let k = H256::from(U256::from(0));
|
||||
let k2 = H256::from(U256::from(1));
|
||||
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(0)));
|
||||
state.clear();
|
||||
|
||||
let c0 = state.checkpoint();
|
||||
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
|
||||
let c1 = state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(1))).unwrap();
|
||||
let c2 = state.checkpoint();
|
||||
let c3 = state.checkpoint();
|
||||
state.set_storage(&a, k2, H256::from(U256::from(3))).unwrap();
|
||||
state.set_storage(&a, k, H256::from(U256::from(3))).unwrap();
|
||||
let c4 = state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(4))).unwrap();
|
||||
let c5 = state.checkpoint();
|
||||
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c4, &a, &k).unwrap(), Some(H256::from(U256::from(3))));
|
||||
assert_eq!(state.checkpoint_storage_at(c5, &a, &k).unwrap(), Some(H256::from(U256::from(4))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c5.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c4, &a, &k).unwrap(), Some(H256::from(U256::from(3))));
|
||||
|
||||
state.revert_to_checkpoint(); // Revert to c4.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c3.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
|
||||
state.revert_to_checkpoint(); // Revert to c2.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c1.
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checkpoint_get_storage_at() {
|
||||
let mut state = get_temp_state();
|
||||
let a = Address::zero();
|
||||
let k = H256::from(U256::from(0));
|
||||
let k2 = H256::from(U256::from(1));
|
||||
|
||||
state.set_storage(&a, k, H256::from(U256::from(0xffff))).unwrap();
|
||||
state.commit().unwrap();
|
||||
state.clear();
|
||||
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(0xffff)));
|
||||
state.clear();
|
||||
|
||||
let cm1 = state.checkpoint();
|
||||
let c0 = state.checkpoint();
|
||||
state.new_contract(&a, U256::zero(), U256::zero()).unwrap();
|
||||
let c1 = state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(1))).unwrap();
|
||||
let c2 = state.checkpoint();
|
||||
let c3 = state.checkpoint();
|
||||
state.set_storage(&a, k2, H256::from(U256::from(3))).unwrap();
|
||||
state.set_storage(&a, k, H256::from(U256::from(3))).unwrap();
|
||||
let c4 = state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(4))).unwrap();
|
||||
let c5 = state.checkpoint();
|
||||
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c4, &a, &k).unwrap(), Some(H256::from(U256::from(3))));
|
||||
assert_eq!(state.checkpoint_storage_at(c5, &a, &k).unwrap(), Some(H256::from(U256::from(4))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c5.
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c4, &a, &k).unwrap(), Some(H256::from(U256::from(3))));
|
||||
|
||||
state.revert_to_checkpoint(); // Revert to c4.
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
assert_eq!(state.checkpoint_storage_at(c3, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c3.
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
assert_eq!(state.checkpoint_storage_at(c2, &a, &k).unwrap(), Some(H256::from(U256::from(1))));
|
||||
|
||||
state.revert_to_checkpoint(); // Revert to c2.
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c1, &a, &k).unwrap(), Some(H256::from(U256::from(0))));
|
||||
|
||||
state.discard_checkpoint(); // Commit/discard c1.
|
||||
assert_eq!(state.checkpoint_storage_at(cm1, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
assert_eq!(state.checkpoint_storage_at(c0, &a, &k).unwrap(), Some(H256::from(U256::from(0xffff))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kill_account_with_checkpoints() {
|
||||
let mut state = get_temp_state();
|
||||
let a = Address::zero();
|
||||
let k = H256::from(U256::from(0));
|
||||
state.checkpoint();
|
||||
state.set_storage(&a, k, H256::from(U256::from(1))).unwrap();
|
||||
state.checkpoint();
|
||||
state.kill_account(&a);
|
||||
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(0)));
|
||||
state.revert_to_checkpoint();
|
||||
assert_eq!(state.storage_at(&a, &k).unwrap(), H256::from(U256::from(1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_empty() {
|
||||
let mut state = get_temp_state();
|
||||
@ -2233,7 +2499,7 @@ mod tests {
|
||||
state.add_balance(&b, &100.into(), CleanupMode::ForceCreate).unwrap(); // create a dust account
|
||||
state.add_balance(&c, &101.into(), CleanupMode::ForceCreate).unwrap(); // create a normal account
|
||||
state.add_balance(&d, &99.into(), CleanupMode::ForceCreate).unwrap(); // create another dust account
|
||||
state.new_contract(&e, 100.into(), 1.into()); // create a contract account
|
||||
state.new_contract(&e, 100.into(), 1.into()).unwrap(); // create a contract account
|
||||
state.init_code(&e, vec![0x00]).unwrap();
|
||||
state.commit().unwrap();
|
||||
state.drop()
|
||||
|
@ -34,8 +34,8 @@ pub struct Substate {
|
||||
/// Any logs.
|
||||
pub logs: Vec<LogEntry>,
|
||||
|
||||
/// Refund counter of SSTORE nonzero -> zero.
|
||||
pub sstore_clears_count: U256,
|
||||
/// Refund counter of SSTORE.
|
||||
pub sstore_clears_refund: U256,
|
||||
|
||||
/// Created contracts.
|
||||
pub contracts_created: Vec<Address>,
|
||||
@ -52,7 +52,7 @@ impl Substate {
|
||||
self.suicides.extend(s.suicides);
|
||||
self.touched.extend(s.touched);
|
||||
self.logs.extend(s.logs);
|
||||
self.sstore_clears_count = self.sstore_clears_count + s.sstore_clears_count;
|
||||
self.sstore_clears_refund = self.sstore_clears_refund + s.sstore_clears_refund;
|
||||
self.contracts_created.extend(s.contracts_created);
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ mod tests {
|
||||
topics: vec![],
|
||||
data: vec![]
|
||||
});
|
||||
sub_state.sstore_clears_count = 5.into();
|
||||
sub_state.sstore_clears_refund = (15000 * 5).into();
|
||||
sub_state.suicides.insert(10u64.into());
|
||||
|
||||
let mut sub_state_2 = Substate::new();
|
||||
@ -96,11 +96,11 @@ mod tests {
|
||||
topics: vec![],
|
||||
data: vec![]
|
||||
});
|
||||
sub_state_2.sstore_clears_count = 7.into();
|
||||
sub_state_2.sstore_clears_refund = (15000 * 7).into();
|
||||
|
||||
sub_state.accrue(sub_state_2);
|
||||
assert_eq!(sub_state.contracts_created.len(), 2);
|
||||
assert_eq!(sub_state.sstore_clears_count, 12.into());
|
||||
assert_eq!(sub_state.sstore_clears_refund, (15000 * 12).into());
|
||||
assert_eq!(sub_state.suicides.len(), 1);
|
||||
}
|
||||
}
|
||||
|
@ -63,6 +63,9 @@ pub enum CreateContractAddress {
|
||||
|
||||
/// Externalities interface for EVMs
|
||||
pub trait Ext {
|
||||
/// Returns the storage value for a given key if reversion happens on the current transaction.
|
||||
fn initial_storage_at(&self, key: &H256) -> Result<H256>;
|
||||
|
||||
/// Returns a value for given key.
|
||||
fn storage_at(&self, key: &H256) -> Result<H256>;
|
||||
|
||||
@ -136,8 +139,11 @@ pub trait Ext {
|
||||
/// then A depth is 0, B is 1, C is 2 and so on.
|
||||
fn depth(&self) -> usize;
|
||||
|
||||
/// Increments sstore refunds count by 1.
|
||||
fn inc_sstore_clears(&mut self);
|
||||
/// Increments sstore refunds counter.
|
||||
fn add_sstore_refund(&mut self, value: U256);
|
||||
|
||||
/// Decrements sstore refunds counter.
|
||||
fn sub_sstore_refund(&mut self, value: U256);
|
||||
|
||||
/// Decide if any more operations should be traced. Passthrough for the VM trace.
|
||||
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _current_gas: U256) -> bool { false }
|
||||
|
@ -117,6 +117,8 @@ pub struct Schedule {
|
||||
pub have_bitwise_shifting: bool,
|
||||
/// Kill basic accounts below this balance if touched.
|
||||
pub kill_dust: CleanDustMode,
|
||||
/// Enable EIP-1283 rules
|
||||
pub eip1283: bool,
|
||||
/// VM execution does not increase null signed address nonce if this field is true.
|
||||
pub keep_unsigned_nonce: bool,
|
||||
/// Wasm extra schedule settings, if wasm activated
|
||||
@ -250,6 +252,7 @@ impl Schedule {
|
||||
blockhash_gas: 20,
|
||||
have_static_call: false,
|
||||
kill_dust: CleanDustMode::Off,
|
||||
eip1283: false,
|
||||
keep_unsigned_nonce: false,
|
||||
wasm: None,
|
||||
}
|
||||
@ -323,6 +326,7 @@ impl Schedule {
|
||||
blockhash_gas: 20,
|
||||
have_static_call: false,
|
||||
kill_dust: CleanDustMode::Off,
|
||||
eip1283: false,
|
||||
keep_unsigned_nonce: false,
|
||||
wasm: None,
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ pub struct FakeExt {
|
||||
pub store: HashMap<H256, H256>,
|
||||
pub suicides: HashSet<Address>,
|
||||
pub calls: HashSet<FakeCall>,
|
||||
pub sstore_clears: usize,
|
||||
pub sstore_clears: U256,
|
||||
pub depth: usize,
|
||||
pub blockhashes: HashMap<U256, H256>,
|
||||
pub codes: HashMap<Address, Arc<Bytes>>,
|
||||
@ -105,6 +105,10 @@ impl FakeExt {
|
||||
}
|
||||
|
||||
impl Ext for FakeExt {
|
||||
fn initial_storage_at(&self, _key: &H256) -> Result<H256> {
|
||||
Ok(H256::new())
|
||||
}
|
||||
|
||||
fn storage_at(&self, key: &H256) -> Result<H256> {
|
||||
Ok(self.store.get(key).unwrap_or(&H256::new()).clone())
|
||||
}
|
||||
@ -216,8 +220,12 @@ impl Ext for FakeExt {
|
||||
self.is_static
|
||||
}
|
||||
|
||||
fn inc_sstore_clears(&mut self) {
|
||||
self.sstore_clears += 1;
|
||||
fn add_sstore_refund(&mut self, value: U256) {
|
||||
self.sstore_clears = self.sstore_clears + value;
|
||||
}
|
||||
|
||||
fn sub_sstore_refund(&mut self, value: U256) {
|
||||
self.sstore_clears = self.sstore_clears - value;
|
||||
}
|
||||
|
||||
fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _gas: U256) -> bool {
|
||||
|
@ -285,7 +285,8 @@ impl<'a> Runtime<'a> {
|
||||
self.ext.set_storage(key, val).map_err(|_| Error::StorageUpdateError)?;
|
||||
|
||||
if former_val != H256::zero() && val == H256::zero() {
|
||||
self.ext.inc_sstore_clears();
|
||||
let sstore_clears_schedule = U256::from(self.schedule().sstore_refund_gas);
|
||||
self.ext.add_sstore_refund(sstore_clears_schedule);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -109,6 +109,8 @@ pub struct Params {
|
||||
#[serde(rename="eip1052Transition")]
|
||||
pub eip1052_transition: Option<Uint>,
|
||||
/// See `CommonParams` docs.
|
||||
#[serde(rename="eip1283Transition")]
|
||||
pub eip1283_transition: Option<Uint>,
|
||||
#[serde(rename="eip1014Transition")]
|
||||
pub eip1014_transition: Option<Uint>,
|
||||
/// See `CommonParams` docs.
|
||||
|
Loading…
Reference in New Issue
Block a user