EIP-3529: Reduction in gas refunds (#406)

* Implement changed gas refund rules according to EIP-3529

* Add a unit test for changed gas refunds

* Add missing comment

* Add fork block to spec.hard_forks
This commit is contained in:
Jochen Müller 2021-05-19 17:18:01 +02:00 committed by GitHub
parent f9f492638c
commit f14d3e5a5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 125 additions and 4 deletions

View File

@ -1463,12 +1463,18 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> {
U256::from(schedule.suicide_refund_gas) * U256::from(substate.suicides.len()); U256::from(schedule.suicide_refund_gas) * U256::from(substate.suicides.len());
let refunds_bound = sstore_refunds + suicide_refunds; let refunds_bound = sstore_refunds + suicide_refunds;
// real ammount to refund // real amount to refund
let gas_left_prerefund = match result { let gas_left_prerefund = match result {
Ok(FinalizationResult { gas_left, .. }) => gas_left, Ok(FinalizationResult { gas_left, .. }) => gas_left,
_ => 0.into(), _ => 0.into(),
}; };
let refunded = cmp::min(refunds_bound, (t.tx().gas - gas_left_prerefund) >> 1); let refunded = if refunds_bound.is_zero() {
refunds_bound
} else {
let gas_used = t.tx().gas - gas_left_prerefund;
let max_refund = gas_used / schedule.max_refund_quotient;
cmp::min(max_refund, refunds_bound)
};
let gas_left = gas_left_prerefund + refunded; let gas_left = gas_left_prerefund + refunded;
let gas_used = t.tx().gas.saturating_sub(gas_left); let gas_used = t.tx().gas.saturating_sub(gas_left);

View File

@ -139,6 +139,8 @@ pub struct CommonParams {
pub eip2929_transition: BlockNumber, pub eip2929_transition: BlockNumber,
/// Number of first block where EIP-2930 rules begin. /// Number of first block where EIP-2930 rules begin.
pub eip2930_transition: BlockNumber, pub eip2930_transition: BlockNumber,
/// Number of first block where EIP-3529 rules begin.
pub eip3529_transition: BlockNumber,
/// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
pub dust_protection_transition: BlockNumber, pub dust_protection_transition: BlockNumber,
/// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
@ -245,6 +247,11 @@ impl CommonParams {
schedule.sload_gas = ::vm::schedule::EIP2929_WARM_STORAGE_READ_COST; schedule.sload_gas = ::vm::schedule::EIP2929_WARM_STORAGE_READ_COST;
schedule.sstore_reset_gas = ::vm::schedule::EIP2929_SSTORE_RESET_GAS; schedule.sstore_reset_gas = ::vm::schedule::EIP2929_SSTORE_RESET_GAS;
} }
if block_number >= self.eip3529_transition {
schedule.suicide_refund_gas = 0;
schedule.sstore_refund_gas = ::vm::schedule::EIP3529_SSTORE_CLEARS_SCHEDULE;
schedule.max_refund_quotient = ::vm::schedule::EIP3529_MAX_REFUND_QUOTIENT;
}
if block_number >= self.dust_protection_transition { if block_number >= self.dust_protection_transition {
schedule.kill_dust = match self.remove_dust_contracts { schedule.kill_dust = match self.remove_dust_contracts {
@ -378,6 +385,9 @@ impl From<ethjson::spec::Params> for CommonParams {
eip2930_transition: p eip2930_transition: p
.eip2930_transition .eip2930_transition
.map_or_else(BlockNumber::max_value, Into::into), .map_or_else(BlockNumber::max_value, Into::into),
eip3529_transition: p
.eip3529_transition
.map_or_else(BlockNumber::max_value, Into::into),
dust_protection_transition: p dust_protection_transition: p
.dust_protection_transition .dust_protection_transition
.map_or_else(BlockNumber::max_value, Into::into), .map_or_else(BlockNumber::max_value, Into::into),
@ -667,6 +677,7 @@ impl Spec {
params.eip2315_transition, params.eip2315_transition,
params.eip2929_transition, params.eip2929_transition,
params.eip2930_transition, params.eip2930_transition,
params.eip3529_transition,
params.dust_protection_transition, params.dust_protection_transition,
params.wasm_activation_transition, params.wasm_activation_transition,
params.wasm_disable_transition, params.wasm_disable_transition,

View File

@ -112,6 +112,8 @@ pub struct Params {
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub eip2930_transition: Option<Uint>, pub eip2930_transition: Option<Uint>,
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub eip3529_transition: Option<Uint>,
/// See `CommonParams` docs.
pub dust_protection_transition: Option<Uint>, pub dust_protection_transition: Option<Uint>,
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub nonce_cap_increment: Option<Uint>, pub nonce_cap_increment: Option<Uint>,

View File

@ -1661,6 +1661,61 @@ fn test_access_list_cheap_expensive_cheap(factory: super::Factory) {
assert_eq!(gas_left, U256::from(0)); assert_eq!(gas_left, U256::from(0));
} }
evm_test! {test_refund_post_london: test_refund_post_london_int}
fn test_refund_post_london(factory: super::Factory) {
// Compare EIP-3529 for the test cases
let code = hex!("60006000556000600055").to_vec();
london_refund_test(&factory, code, &[], 0);
let code = hex!("60006000556001600055").to_vec();
london_refund_test(&factory, code, &[], 0);
let code = hex!("60016000556000600055").to_vec();
london_refund_test(&factory, code, &[], 19900);
let code = hex!("60006000556000600055").to_vec();
london_refund_test(&factory, code, &[1], 4800);
let code = hex!("60006000556001600055").to_vec();
london_refund_test(&factory, code, &[1], 2800);
let code = hex!("60006000556002600055").to_vec();
london_refund_test(&factory, code, &[1], 0);
let code = hex!("60026000556000600055").to_vec();
london_refund_test(&factory, code, &[1], 4800);
let code = hex!("60026000556001600055").to_vec();
london_refund_test(&factory, code, &[1], 2800);
let code = hex!("60016000556000600055").to_vec();
london_refund_test(&factory, code, &[], 19900);
let code = hex!("600060005560016000556000600055").to_vec();
london_refund_test(&factory, code, &[1], 7600);
}
fn london_refund_test(
factory: &super::Factory,
code: Vec<u8>,
fill: &[u64],
expected_refund: i128,
) {
let mut params = ActionParams::default();
params.gas = U256::from(22318);
params.code = Some(Arc::new(code));
let mut ext = FakeExt::new_london(
Address::from_str("0000000000000000000000000000000000000000").unwrap(),
Address::from_str("000000000000000000000000636F6E7472616374").unwrap(),
&[],
);
ext.prefill(fill);
let vm = factory.create(params, ext.schedule(), ext.depth());
vm.exec(&mut ext).ok().unwrap().unwrap();
assert_eq!(ext.sstore_clears, expected_refund);
}
fn push_two_pop_one_constantinople_test( fn push_two_pop_one_constantinople_test(
factory: &super::Factory, factory: &super::Factory,
opcode: u8, opcode: u8,

View File

@ -28,6 +28,12 @@ pub const EIP2929_SSTORE_RESET_GAS: usize = 5000 - EIP2929_COLD_SLOAD_COST;
pub const EIP2930_ACCESS_LIST_STORAGE_KEY_COST: usize = 1900; pub const EIP2930_ACCESS_LIST_STORAGE_KEY_COST: usize = 1900;
/// Gas per received address /// Gas per received address
pub const EIP2930_ACCESS_LIST_ADDRESS_COST: usize = 2400; pub const EIP2930_ACCESS_LIST_ADDRESS_COST: usize = 2400;
/// Gas used per transaction divided by this number is the maximum refundable amount
pub const MAX_REFUND_QUOTIENT: usize = 2;
pub const EIP3529_MAX_REFUND_QUOTIENT: usize = 5;
/// Reduced SSTORE refund as by EIP-3529
pub const EIP3529_SSTORE_CLEARS_SCHEDULE: usize =
EIP2929_SSTORE_RESET_GAS + EIP2930_ACCESS_LIST_STORAGE_KEY_COST;
/// Definition of the cost schedule and other parameterisations for the EVM. /// Definition of the cost schedule and other parameterisations for the EVM.
#[derive(Debug)] #[derive(Debug)]
@ -155,6 +161,8 @@ pub struct Schedule {
pub eip2929: bool, pub eip2929: bool,
/// Enable EIP-2930 rules for optional access list transactions. it depends on EIP-2929 /// Enable EIP-2930 rules for optional access list transactions. it depends on EIP-2929
pub eip2930: bool, pub eip2930: bool,
/// Gas used in transaction divided by this number is the maximum refundable amount.
pub max_refund_quotient: usize,
} }
/// Wasm cost table /// Wasm cost table
@ -302,6 +310,7 @@ impl Schedule {
wasm: None, wasm: None,
eip2929: false, eip2929: false,
eip2930: false, eip2930: false,
max_refund_quotient: MAX_REFUND_QUOTIENT,
} }
} }
@ -359,6 +368,17 @@ impl Schedule {
schedule schedule
} }
pub fn new_london() -> Schedule {
let mut schedule = Self::new_berlin();
// EIP-3529 changes
schedule.suicide_refund_gas = 0;
schedule.sstore_refund_gas = EIP3529_SSTORE_CLEARS_SCHEDULE;
schedule.max_refund_quotient = EIP3529_MAX_REFUND_QUOTIENT;
schedule
}
fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule { fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
Schedule { Schedule {
exceptional_failed_code_deposit: efcd, exceptional_failed_code_deposit: efcd,
@ -422,6 +442,7 @@ impl Schedule {
wasm: None, wasm: None,
eip2929: false, eip2929: false,
eip2930: false, eip2930: false,
max_refund_quotient: MAX_REFUND_QUOTIENT,
} }
} }

View File

@ -63,6 +63,7 @@ pub struct FakeCall {
/// Can't do recursive calls. /// Can't do recursive calls.
#[derive(Default)] #[derive(Default)]
pub struct FakeExt { pub struct FakeExt {
pub initial_store: HashMap<H256, H256>,
pub store: HashMap<H256, H256>, pub store: HashMap<H256, H256>,
pub suicides: HashSet<Address>, pub suicides: HashSet<Address>,
pub calls: HashSet<FakeCall>, pub calls: HashSet<FakeCall>,
@ -130,6 +131,12 @@ impl FakeExt {
ext ext
} }
pub fn new_london(from: Address, to: Address, builtins: &[Address]) -> Self {
let mut ext = FakeExt::new_berlin(from, to, builtins);
ext.schedule = Schedule::new_london();
ext
}
/// Alter fake externalities to allow wasm /// Alter fake externalities to allow wasm
pub fn with_wasm(mut self) -> Self { pub fn with_wasm(mut self) -> Self {
self.schedule.wasm = Some(Default::default()); self.schedule.wasm = Some(Default::default());
@ -141,11 +148,30 @@ impl FakeExt {
self.chain_id = chain_id; self.chain_id = chain_id;
self self
} }
pub fn set_initial_storage(&mut self, key: H256, value: H256) {
self.initial_store.insert(key, value);
}
/// Fill the storage before the transaction with `data`, i. e. set
/// both original and current values, beginning from address 0.
pub fn prefill(&mut self, data: &[u64]) {
for (k, v) in data.iter().enumerate() {
let key = H256::from_low_u64_be(k as u64);
let value = H256::from_low_u64_be(*v);
self.set_initial_storage(key, value);
self.set_storage(key, value)
.expect("FakeExt::set_storage() never returns an Err.");
}
}
} }
impl Ext for FakeExt { impl Ext for FakeExt {
fn initial_storage_at(&self, _key: &H256) -> Result<H256> { fn initial_storage_at(&self, key: &H256) -> Result<H256> {
Ok(H256::default()) match self.initial_store.get(key) {
Some(value) => Ok(*value),
None => Ok(H256::default()),
}
} }
fn storage_at(&self, key: &H256) -> Result<H256> { fn storage_at(&self, key: &H256) -> Result<H256> {