EIP-2315: Simple Subroutines for the EVM (#11629)
This commit is contained in:
		
							parent
							
								
									751210c963
								
							
						
					
					
						commit
						1460f6cc27
					
				@ -321,6 +321,13 @@ enum_with_from_u8! {
 | 
			
		||||
        #[doc = "Makes a log entry, 4 topics."]
 | 
			
		||||
        LOG4 = 0xa4,
 | 
			
		||||
 | 
			
		||||
        #[doc = "Marks the entry point to a subroutine."]
 | 
			
		||||
        BEGINSUB = 0x5c,
 | 
			
		||||
        #[doc = "Returns from a subroutine."]
 | 
			
		||||
        RETURNSUB = 0x5d,
 | 
			
		||||
        #[doc = "Jumps to a defined BEGINSUB subroutine."]
 | 
			
		||||
        JUMPSUB = 0x5e,
 | 
			
		||||
 | 
			
		||||
        #[doc = "create a new account with associated code"]
 | 
			
		||||
        CREATE = 0xf0,
 | 
			
		||||
        #[doc = "message-call into an account"]
 | 
			
		||||
@ -591,6 +598,9 @@ lazy_static! {
 | 
			
		||||
        arr[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special));
 | 
			
		||||
        arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 0, GasPriceTier::Special));
 | 
			
		||||
        arr[LOG4 as usize] = Some(InstructionInfo::new("LOG4", 6, 0, GasPriceTier::Special));
 | 
			
		||||
        arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base));
 | 
			
		||||
        arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High));
 | 
			
		||||
        arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low));
 | 
			
		||||
        arr[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special));
 | 
			
		||||
        arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 1, GasPriceTier::Special));
 | 
			
		||||
        arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special));
 | 
			
		||||
 | 
			
		||||
@ -61,6 +61,10 @@ const TWO_POW_96: U256 = U256([0, 0x100000000, 0, 0]); //0x1 00000000 00000000 0
 | 
			
		||||
const TWO_POW_224: U256 = U256([0, 0, 0, 0x100000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000
 | 
			
		||||
const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 000000
 | 
			
		||||
 | 
			
		||||
/// Maximum subroutine stack size as specified in
 | 
			
		||||
/// https://eips.ethereum.org/EIPS/eip-2315.
 | 
			
		||||
pub const MAX_SUB_STACK_SIZE: usize = 1023;
 | 
			
		||||
 | 
			
		||||
fn to_biguint(x: U256) -> BigUint {
 | 
			
		||||
    let mut bytes = [0u8; 32];
 | 
			
		||||
    x.to_little_endian(&mut bytes);
 | 
			
		||||
@ -101,6 +105,8 @@ enum InstructionResult<Gas> {
 | 
			
		||||
    Ok,
 | 
			
		||||
    UnusedGas(Gas),
 | 
			
		||||
    JumpToPosition(U256),
 | 
			
		||||
    JumpToSubroutine(U256),
 | 
			
		||||
    ReturnFromSubroutine(usize),
 | 
			
		||||
    StopExecutionNeedsReturn {
 | 
			
		||||
        /// Gas left.
 | 
			
		||||
        gas: Gas,
 | 
			
		||||
@ -183,8 +189,10 @@ pub struct Interpreter<Cost: CostType> {
 | 
			
		||||
    do_trace: bool,
 | 
			
		||||
    done: bool,
 | 
			
		||||
    valid_jump_destinations: Option<Arc<BitSet>>,
 | 
			
		||||
    valid_subroutine_destinations: Option<Arc<BitSet>>,
 | 
			
		||||
    gasometer: Option<Gasometer<Cost>>,
 | 
			
		||||
    stack: VecStack<U256>,
 | 
			
		||||
    return_stack: Vec<usize>,
 | 
			
		||||
    resume_output_range: Option<(U256, U256)>,
 | 
			
		||||
    resume_result: Option<InstructionResult<Cost>>,
 | 
			
		||||
    last_stack_ret_len: usize,
 | 
			
		||||
@ -290,10 +298,12 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
        let params = InterpreterParams::from(params);
 | 
			
		||||
        let informant = informant::EvmInformant::new(depth);
 | 
			
		||||
        let valid_jump_destinations = None;
 | 
			
		||||
        let valid_subroutine_destinations = None;
 | 
			
		||||
        let gasometer = Cost::from_u256(params.gas)
 | 
			
		||||
            .ok()
 | 
			
		||||
            .map(|gas| Gasometer::<Cost>::new(gas));
 | 
			
		||||
        let stack = VecStack::with_capacity(schedule.stack_limit, U256::zero());
 | 
			
		||||
        let return_stack = Vec::with_capacity(MAX_SUB_STACK_SIZE);
 | 
			
		||||
 | 
			
		||||
        Interpreter {
 | 
			
		||||
            cache,
 | 
			
		||||
@ -301,8 +311,10 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
            reader,
 | 
			
		||||
            informant,
 | 
			
		||||
            valid_jump_destinations,
 | 
			
		||||
            valid_subroutine_destinations,
 | 
			
		||||
            gasometer,
 | 
			
		||||
            stack,
 | 
			
		||||
            return_stack,
 | 
			
		||||
            done: false,
 | 
			
		||||
            // Overridden in `step_inner` based on
 | 
			
		||||
            // the result of `ext.trace_next_instruction`.
 | 
			
		||||
@ -478,7 +490,8 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
                if self.valid_jump_destinations.is_none() {
 | 
			
		||||
                    self.valid_jump_destinations = Some(
 | 
			
		||||
                        self.cache
 | 
			
		||||
                            .jump_destinations(&self.params.code_hash, &self.reader.code),
 | 
			
		||||
                            .jump_and_sub_destinations(&self.params.code_hash, &self.reader.code)
 | 
			
		||||
                            .0,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                let jump_destinations = self
 | 
			
		||||
@ -491,6 +504,28 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
                };
 | 
			
		||||
                self.reader.position = pos;
 | 
			
		||||
            }
 | 
			
		||||
            InstructionResult::JumpToSubroutine(position) => {
 | 
			
		||||
                if self.valid_subroutine_destinations.is_none() {
 | 
			
		||||
                    self.valid_subroutine_destinations = Some(
 | 
			
		||||
                        self.cache
 | 
			
		||||
                            .jump_and_sub_destinations(&self.params.code_hash, &self.reader.code)
 | 
			
		||||
                            .1,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                let subroutine_destinations = self
 | 
			
		||||
                    .valid_subroutine_destinations
 | 
			
		||||
                    .as_ref()
 | 
			
		||||
                    .expect("subroutine_destinations are initialized on first jump; qed");
 | 
			
		||||
                let pos = match self.verify_jump(position, subroutine_destinations) {
 | 
			
		||||
                    Ok(x) => x,
 | 
			
		||||
                    Err(e) => return InterpreterResult::Done(Err(e)),
 | 
			
		||||
                };
 | 
			
		||||
                self.return_stack.push(self.reader.position);
 | 
			
		||||
                self.reader.position = pos + 1;
 | 
			
		||||
            }
 | 
			
		||||
            InstructionResult::ReturnFromSubroutine(pos) => {
 | 
			
		||||
                self.reader.position = pos;
 | 
			
		||||
            }
 | 
			
		||||
            InstructionResult::StopExecutionNeedsReturn {
 | 
			
		||||
                gas,
 | 
			
		||||
                init_off,
 | 
			
		||||
@ -537,20 +572,20 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
    ) -> vm::Result<()> {
 | 
			
		||||
        let schedule = ext.schedule();
 | 
			
		||||
 | 
			
		||||
        if (instruction == instructions::DELEGATECALL && !schedule.have_delegate_call)
 | 
			
		||||
            || (instruction == instructions::CREATE2 && !schedule.have_create2)
 | 
			
		||||
            || (instruction == instructions::STATICCALL && !schedule.have_static_call)
 | 
			
		||||
            || ((instruction == instructions::RETURNDATACOPY
 | 
			
		||||
                || instruction == instructions::RETURNDATASIZE)
 | 
			
		||||
        use instructions::*;
 | 
			
		||||
        if (instruction == DELEGATECALL && !schedule.have_delegate_call)
 | 
			
		||||
            || (instruction == CREATE2 && !schedule.have_create2)
 | 
			
		||||
            || (instruction == STATICCALL && !schedule.have_static_call)
 | 
			
		||||
            || ((instruction == RETURNDATACOPY || instruction == RETURNDATASIZE)
 | 
			
		||||
                && !schedule.have_return_data)
 | 
			
		||||
            || (instruction == instructions::REVERT && !schedule.have_revert)
 | 
			
		||||
            || ((instruction == instructions::SHL
 | 
			
		||||
                || instruction == instructions::SHR
 | 
			
		||||
                || instruction == instructions::SAR)
 | 
			
		||||
            || (instruction == REVERT && !schedule.have_revert)
 | 
			
		||||
            || ((instruction == SHL || instruction == SHR || instruction == SAR)
 | 
			
		||||
                && !schedule.have_bitwise_shifting)
 | 
			
		||||
            || (instruction == instructions::EXTCODEHASH && !schedule.have_extcodehash)
 | 
			
		||||
            || (instruction == instructions::CHAINID && !schedule.have_chain_id)
 | 
			
		||||
            || (instruction == instructions::SELFBALANCE && !schedule.have_selfbalance)
 | 
			
		||||
            || (instruction == EXTCODEHASH && !schedule.have_extcodehash)
 | 
			
		||||
            || (instruction == CHAINID && !schedule.have_chain_id)
 | 
			
		||||
            || (instruction == SELFBALANCE && !schedule.have_selfbalance)
 | 
			
		||||
            || ((instruction == BEGINSUB || instruction == JUMPSUB || instruction == RETURNSUB)
 | 
			
		||||
                && !schedule.have_subs)
 | 
			
		||||
        {
 | 
			
		||||
            return Err(vm::Error::BadInstruction {
 | 
			
		||||
                instruction: instruction as u8,
 | 
			
		||||
@ -623,6 +658,29 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
            instructions::JUMPDEST => {
 | 
			
		||||
                // ignore
 | 
			
		||||
            }
 | 
			
		||||
            instructions::BEGINSUB => {
 | 
			
		||||
                return Err(vm::Error::InvalidSubEntry);
 | 
			
		||||
            }
 | 
			
		||||
            instructions::JUMPSUB => {
 | 
			
		||||
                if self.return_stack.len() >= MAX_SUB_STACK_SIZE {
 | 
			
		||||
                    return Err(vm::Error::OutOfSubStack {
 | 
			
		||||
                        wanted: 1,
 | 
			
		||||
                        limit: MAX_SUB_STACK_SIZE,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                let sub_destination = self.stack.pop_back();
 | 
			
		||||
                return Ok(InstructionResult::JumpToSubroutine(sub_destination));
 | 
			
		||||
            }
 | 
			
		||||
            instructions::RETURNSUB => {
 | 
			
		||||
                if let Some(pos) = self.return_stack.pop() {
 | 
			
		||||
                    return Ok(InstructionResult::ReturnFromSubroutine(pos));
 | 
			
		||||
                } else {
 | 
			
		||||
                    return Err(vm::Error::SubStackUnderflow {
 | 
			
		||||
                        wanted: 1,
 | 
			
		||||
                        on_stack: 0,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            instructions::CREATE | instructions::CREATE2 => {
 | 
			
		||||
                let endowment = self.stack.pop_back();
 | 
			
		||||
                let init_off = self.stack.pop_back();
 | 
			
		||||
@ -1413,6 +1471,7 @@ impl<Cost: CostType> Interpreter<Cost> {
 | 
			
		||||
        if valid_jump_destinations.contains(jump) && U256::from(jump) == jump_u {
 | 
			
		||||
            Ok(jump)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Note: if jump > usize, BadJumpDestination value is trimmed
 | 
			
		||||
            Err(vm::Error::BadJumpDestination { destination: jump })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ use std::sync::Arc;
 | 
			
		||||
const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
 | 
			
		||||
 | 
			
		||||
// stub for a HeapSizeOf implementation.
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
struct Bits(Arc<BitSet>);
 | 
			
		||||
 | 
			
		||||
impl HeapSizeOf for Bits {
 | 
			
		||||
@ -35,9 +36,21 @@ impl HeapSizeOf for Bits {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone)]
 | 
			
		||||
struct CacheItem {
 | 
			
		||||
    jump_destination: Bits,
 | 
			
		||||
    sub_entrypoint: Bits,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl HeapSizeOf for CacheItem {
 | 
			
		||||
    fn heap_size_of_children(&self) -> usize {
 | 
			
		||||
        self.jump_destination.heap_size_of_children() + self.sub_entrypoint.heap_size_of_children()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Global cache for EVM interpreter
 | 
			
		||||
pub struct SharedCache {
 | 
			
		||||
    jump_destinations: Mutex<MemoryLruCache<H256, Bits>>,
 | 
			
		||||
    jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SharedCache {
 | 
			
		||||
@ -50,47 +63,62 @@ impl SharedCache {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get jump destinations bitmap for a contract.
 | 
			
		||||
    pub fn jump_destinations(&self, code_hash: &Option<H256>, code: &[u8]) -> Arc<BitSet> {
 | 
			
		||||
    pub fn jump_and_sub_destinations(
 | 
			
		||||
        &self,
 | 
			
		||||
        code_hash: &Option<H256>,
 | 
			
		||||
        code: &[u8],
 | 
			
		||||
    ) -> (Arc<BitSet>, Arc<BitSet>) {
 | 
			
		||||
        if let Some(ref code_hash) = code_hash {
 | 
			
		||||
            if code_hash == &KECCAK_EMPTY {
 | 
			
		||||
                return Self::find_jump_destinations(code);
 | 
			
		||||
                let cache_item = Self::find_jump_and_sub_destinations(code);
 | 
			
		||||
                return (cache_item.jump_destination.0, cache_item.sub_entrypoint.0);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
 | 
			
		||||
                return d.0.clone();
 | 
			
		||||
                return (d.jump_destination.0.clone(), d.sub_entrypoint.0.clone());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let d = Self::find_jump_destinations(code);
 | 
			
		||||
        let d = Self::find_jump_and_sub_destinations(code);
 | 
			
		||||
 | 
			
		||||
        if let Some(ref code_hash) = code_hash {
 | 
			
		||||
            self.jump_destinations
 | 
			
		||||
                .lock()
 | 
			
		||||
                .insert(*code_hash, Bits(d.clone()));
 | 
			
		||||
            self.jump_destinations.lock().insert(*code_hash, d.clone());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        d
 | 
			
		||||
        (d.jump_destination.0, d.sub_entrypoint.0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn find_jump_destinations(code: &[u8]) -> Arc<BitSet> {
 | 
			
		||||
    fn find_jump_and_sub_destinations(code: &[u8]) -> CacheItem {
 | 
			
		||||
        let mut jump_dests = BitSet::with_capacity(code.len());
 | 
			
		||||
        let mut sub_entrypoints = BitSet::with_capacity(code.len());
 | 
			
		||||
        let mut position = 0;
 | 
			
		||||
 | 
			
		||||
        while position < code.len() {
 | 
			
		||||
            let instruction = Instruction::from_u8(code[position]);
 | 
			
		||||
 | 
			
		||||
            if let Some(instruction) = instruction {
 | 
			
		||||
                if instruction == instructions::JUMPDEST {
 | 
			
		||||
                    jump_dests.insert(position);
 | 
			
		||||
                } else if let Some(push_bytes) = instruction.push_bytes() {
 | 
			
		||||
                    position += push_bytes;
 | 
			
		||||
                match instruction {
 | 
			
		||||
                    instructions::JUMPDEST => {
 | 
			
		||||
                        jump_dests.insert(position);
 | 
			
		||||
                    }
 | 
			
		||||
                    instructions::BEGINSUB => {
 | 
			
		||||
                        sub_entrypoints.insert(position);
 | 
			
		||||
                    }
 | 
			
		||||
                    _ => {
 | 
			
		||||
                        if let Some(push_bytes) = instruction.push_bytes() {
 | 
			
		||||
                            position += push_bytes;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            position += 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        jump_dests.shrink_to_fit();
 | 
			
		||||
        Arc::new(jump_dests)
 | 
			
		||||
        CacheItem {
 | 
			
		||||
            jump_destination: Bits(Arc::new(jump_dests)),
 | 
			
		||||
            sub_entrypoint: Bits(Arc::new(sub_entrypoints)),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -100,15 +128,92 @@ impl Default for SharedCache {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_find_jump_destinations() {
 | 
			
		||||
    use rustc_hex::FromHex;
 | 
			
		||||
    // given
 | 
			
		||||
    let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use hex_literal::hex;
 | 
			
		||||
 | 
			
		||||
    // when
 | 
			
		||||
    let valid_jump_destinations = SharedCache::find_jump_destinations(&code);
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_find_jump_destinations() {
 | 
			
		||||
        // given
 | 
			
		||||
 | 
			
		||||
    // then
 | 
			
		||||
    assert!(valid_jump_destinations.contains(66));
 | 
			
		||||
        // 0000 7F   PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 | 
			
		||||
        // 0021 7F   PUSH32 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
 | 
			
		||||
        // 0042 5B   JUMPDEST
 | 
			
		||||
        // 0043 01   ADD
 | 
			
		||||
        // 0044 60   PUSH1 0x00
 | 
			
		||||
        // 0046 55   SSTORE
 | 
			
		||||
        let code = hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055");
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
 | 
			
		||||
 | 
			
		||||
        // then
 | 
			
		||||
        assert!(cache_item
 | 
			
		||||
            .jump_destination
 | 
			
		||||
            .0
 | 
			
		||||
            .iter()
 | 
			
		||||
            .eq(vec![66].into_iter()));
 | 
			
		||||
        assert!(cache_item.sub_entrypoint.0.is_empty());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_find_jump_destinations_not_in_data_segments() {
 | 
			
		||||
        // given
 | 
			
		||||
 | 
			
		||||
        // 0000 60 06   PUSH1 06
 | 
			
		||||
        // 0002 56      JUMP
 | 
			
		||||
        // 0003 50 5B   PUSH1 0x5B
 | 
			
		||||
        // 0005 56      STOP
 | 
			
		||||
        // 0006 5B      JUMPDEST
 | 
			
		||||
        // 0007 60 04   PUSH1 04
 | 
			
		||||
        // 0009 56      JUMP
 | 
			
		||||
        let code = hex!("600656605B565B6004");
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
 | 
			
		||||
 | 
			
		||||
        // then
 | 
			
		||||
        assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter()));
 | 
			
		||||
        assert!(cache_item.sub_entrypoint.0.is_empty());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_find_sub_entrypoints() {
 | 
			
		||||
        // given
 | 
			
		||||
 | 
			
		||||
        // see https://eips.ethereum.org/EIPS/eip-2315 for disassembly
 | 
			
		||||
        let code = hex!("6800000000000000000c5e005c60115e5d5c5d");
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
 | 
			
		||||
 | 
			
		||||
        // then
 | 
			
		||||
        assert!(cache_item.jump_destination.0.is_empty());
 | 
			
		||||
        assert!(cache_item
 | 
			
		||||
            .sub_entrypoint
 | 
			
		||||
            .0
 | 
			
		||||
            .iter()
 | 
			
		||||
            .eq(vec![12, 17].into_iter()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_find_jump_and_sub_allowing_unknown_opcodes() {
 | 
			
		||||
        // precondition
 | 
			
		||||
        assert!(Instruction::from_u8(0xcc) == None);
 | 
			
		||||
 | 
			
		||||
        // given
 | 
			
		||||
 | 
			
		||||
        // 0000 5B   JUMPDEST
 | 
			
		||||
        // 0001 CC   ???
 | 
			
		||||
        // 0002 5C   BEGINSUB
 | 
			
		||||
        let code = hex!("5BCC5C");
 | 
			
		||||
 | 
			
		||||
        // when
 | 
			
		||||
        let cache_item = SharedCache::find_jump_and_sub_destinations(&code);
 | 
			
		||||
 | 
			
		||||
        // then
 | 
			
		||||
        assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter()));
 | 
			
		||||
        assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@
 | 
			
		||||
// You should have received a copy of the GNU General Public License
 | 
			
		||||
// along with Parity Ethereum.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
use super::interpreter::MAX_SUB_STACK_SIZE;
 | 
			
		||||
use ethereum_types::{Address, H256, U256};
 | 
			
		||||
use factory::Factory;
 | 
			
		||||
use hex_literal::hex;
 | 
			
		||||
@ -1037,6 +1038,171 @@ fn test_jumps(factory: super::Factory) {
 | 
			
		||||
    assert_eq!(gas_left, U256::from(54_117));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_simple: test_subs_simple_int}
 | 
			
		||||
fn test_subs_simple(factory: super::Factory) {
 | 
			
		||||
    // as defined in https://eips.ethereum.org/EIPS/eip-2315
 | 
			
		||||
    let code = hex!("60045e005c5d").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(18);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let gas_left = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    assert_eq!(gas_left, U256::from(0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_two_levels: test_subs_two_levels_int}
 | 
			
		||||
fn test_subs_two_levels(factory: super::Factory) {
 | 
			
		||||
    // as defined in https://eips.ethereum.org/EIPS/eip-2315
 | 
			
		||||
    let code = hex!("6800000000000000000c5e005c60115e5d5c5d").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(36);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let gas_left = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    assert_eq!(gas_left, U256::from(0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_invalid_jump: test_subs_invalid_jump_int}
 | 
			
		||||
fn test_subs_invalid_jump(factory: super::Factory) {
 | 
			
		||||
    // as defined in https://eips.ethereum.org/EIPS/eip-2315
 | 
			
		||||
    let code = hex!("6801000000000000000c5e005c60115e5d5c5d").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(24);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let current = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap())
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let expected = Result::Err(vm::Error::BadJumpDestination { destination: 0xc });
 | 
			
		||||
    assert_eq!(current, expected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_shallow_return_stack: test_subs_shallow_return_stack_int}
 | 
			
		||||
fn test_subs_shallow_return_stack(factory: super::Factory) {
 | 
			
		||||
    // as defined in https://eips.ethereum.org/EIPS/eip-2315
 | 
			
		||||
    let code = hex!("5d5858").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(24);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let current = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap())
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let expected = Result::Err(vm::Error::SubStackUnderflow {
 | 
			
		||||
        wanted: 1,
 | 
			
		||||
        on_stack: 0,
 | 
			
		||||
    });
 | 
			
		||||
    assert_eq!(current, expected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_substack_limit: test_subs_substack_limit_int}
 | 
			
		||||
fn test_subs_substack_limit(factory: super::Factory) {
 | 
			
		||||
    //    PUSH <recursion_limit>
 | 
			
		||||
    //    JUMP a
 | 
			
		||||
    // s: BEGINSUB
 | 
			
		||||
    // a: JUMPDEST
 | 
			
		||||
    //    DUP1
 | 
			
		||||
    //    JUMPI c
 | 
			
		||||
    //    STOP
 | 
			
		||||
    // c: JUMPDEST
 | 
			
		||||
    //    PUSH1 1
 | 
			
		||||
    //    SWAP
 | 
			
		||||
    //    SUB
 | 
			
		||||
    //    JUMPSUB s
 | 
			
		||||
 | 
			
		||||
    let mut code = hex!("6104006007565c5b80600d57005b6001900360065e").to_vec();
 | 
			
		||||
    code[1..3].copy_from_slice(&(MAX_SUB_STACK_SIZE as u16).to_be_bytes()[..]);
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(1_000_000);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let gas_left = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    assert_eq!(gas_left, U256::from(959_049));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_substack_out: test_subs_substack_out_int}
 | 
			
		||||
fn test_subs_substack_out(factory: super::Factory) {
 | 
			
		||||
    let mut code = hex!("6104006007565c5b80600d57005b6001900360065e").to_vec();
 | 
			
		||||
    code[1..3].copy_from_slice(&((MAX_SUB_STACK_SIZE + 1) as u16).to_be_bytes()[..]);
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(1_000_000);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let current = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap())
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let expected = Result::Err(vm::Error::OutOfSubStack {
 | 
			
		||||
        wanted: 1,
 | 
			
		||||
        limit: MAX_SUB_STACK_SIZE,
 | 
			
		||||
    });
 | 
			
		||||
    assert_eq!(current, expected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_sub_at_end: test_subs_sub_at_end_int}
 | 
			
		||||
fn test_subs_sub_at_end(factory: super::Factory) {
 | 
			
		||||
    let code = hex!("6005565c5d5b60035e").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(30);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let gas_left = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    assert_eq!(gas_left, U256::from(0));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_subs_walk_into_subroutine: test_subs_walk_into_subroutine_int}
 | 
			
		||||
fn test_subs_walk_into_subroutine(factory: super::Factory) {
 | 
			
		||||
    let code = hex!("5c5d00").to_vec();
 | 
			
		||||
 | 
			
		||||
    let mut params = ActionParams::default();
 | 
			
		||||
    params.gas = U256::from(100);
 | 
			
		||||
    params.code = Some(Arc::new(code));
 | 
			
		||||
    let mut ext = FakeExt::new_berlin();
 | 
			
		||||
 | 
			
		||||
    let current = {
 | 
			
		||||
        let vm = factory.create(params, ext.schedule(), ext.depth());
 | 
			
		||||
        test_finalize(vm.exec(&mut ext).ok().unwrap())
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let expected = Result::Err(vm::Error::InvalidSubEntry);
 | 
			
		||||
    assert_eq!(current, expected);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
evm_test! {test_calls: test_calls_int}
 | 
			
		||||
fn test_calls(factory: super::Factory) {
 | 
			
		||||
    let code = "600054602d57600160005560006000600060006050610998610100f160006000600060006050610998610100f25b".from_hex().unwrap();
 | 
			
		||||
 | 
			
		||||
@ -435,6 +435,9 @@ impl<'a> CallCreateExecutive<'a> {
 | 
			
		||||
            | Err(vm::Error::MutableCallInStaticContext)
 | 
			
		||||
            | Err(vm::Error::OutOfBounds)
 | 
			
		||||
            | Err(vm::Error::Reverted)
 | 
			
		||||
            | Err(vm::Error::SubStackUnderflow { .. })
 | 
			
		||||
            | Err(vm::Error::OutOfSubStack { .. })
 | 
			
		||||
            | Err(vm::Error::InvalidSubEntry)
 | 
			
		||||
            | Ok(FinalizationResult {
 | 
			
		||||
                apply_state: false, ..
 | 
			
		||||
            }) => {
 | 
			
		||||
 | 
			
		||||
@ -126,6 +126,8 @@ pub struct CommonParams {
 | 
			
		||||
    pub eip1884_transition: BlockNumber,
 | 
			
		||||
    /// Number of first block where EIP-2028 rules begin.
 | 
			
		||||
    pub eip2028_transition: BlockNumber,
 | 
			
		||||
    /// Number of first block where EIP-2315 rules begin.
 | 
			
		||||
    pub eip2315_transition: BlockNumber,
 | 
			
		||||
    /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin.
 | 
			
		||||
    pub dust_protection_transition: BlockNumber,
 | 
			
		||||
    /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled.
 | 
			
		||||
@ -197,6 +199,7 @@ impl CommonParams {
 | 
			
		||||
            && !(block_number >= self.eip1283_disable_transition))
 | 
			
		||||
            || block_number >= self.eip1283_reenable_transition;
 | 
			
		||||
        schedule.eip1706 = block_number >= self.eip1706_transition;
 | 
			
		||||
        schedule.have_subs = block_number >= self.eip2315_transition;
 | 
			
		||||
 | 
			
		||||
        if block_number >= self.eip1884_transition {
 | 
			
		||||
            schedule.have_selfbalance = true;
 | 
			
		||||
@ -329,6 +332,9 @@ impl From<ethjson::spec::Params> for CommonParams {
 | 
			
		||||
            eip2028_transition: p
 | 
			
		||||
                .eip2028_transition
 | 
			
		||||
                .map_or_else(BlockNumber::max_value, Into::into),
 | 
			
		||||
            eip2315_transition: p
 | 
			
		||||
                .eip2315_transition
 | 
			
		||||
                .map_or_else(BlockNumber::max_value, Into::into),
 | 
			
		||||
            dust_protection_transition: p
 | 
			
		||||
                .dust_protection_transition
 | 
			
		||||
                .map_or_else(BlockNumber::max_value, Into::into),
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,12 @@ pub enum Error {
 | 
			
		||||
    StackUnderflow,
 | 
			
		||||
    /// When execution would exceed defined Stack Limit
 | 
			
		||||
    OutOfStack,
 | 
			
		||||
    /// When there is not enough subroutine stack elements to return from
 | 
			
		||||
    SubStackUnderflow,
 | 
			
		||||
    /// When execution would exceed defined subroutine Stack Limit
 | 
			
		||||
    OutOfSubStack,
 | 
			
		||||
    /// When the code walks into a subroutine, that is not allowed
 | 
			
		||||
    InvalidSubEntry,
 | 
			
		||||
    /// When builtin contract failed on input data
 | 
			
		||||
    BuiltIn,
 | 
			
		||||
    /// Returned on evm internal error. Should never be ignored during development.
 | 
			
		||||
@ -57,6 +63,9 @@ impl<'a> From<&'a VmError> for Error {
 | 
			
		||||
            VmError::BadInstruction { .. } => Error::BadInstruction,
 | 
			
		||||
            VmError::StackUnderflow { .. } => Error::StackUnderflow,
 | 
			
		||||
            VmError::OutOfStack { .. } => Error::OutOfStack,
 | 
			
		||||
            VmError::SubStackUnderflow { .. } => Error::SubStackUnderflow,
 | 
			
		||||
            VmError::OutOfSubStack { .. } => Error::OutOfSubStack,
 | 
			
		||||
            VmError::InvalidSubEntry { .. } => Error::InvalidSubEntry,
 | 
			
		||||
            VmError::BuiltIn { .. } => Error::BuiltIn,
 | 
			
		||||
            VmError::Wasm { .. } => Error::Wasm,
 | 
			
		||||
            VmError::Internal(_) => Error::Internal,
 | 
			
		||||
@ -82,7 +91,10 @@ impl fmt::Display for Error {
 | 
			
		||||
            BadInstruction => "Bad instruction",
 | 
			
		||||
            StackUnderflow => "Stack underflow",
 | 
			
		||||
            OutOfStack => "Out of stack",
 | 
			
		||||
            SubStackUnderflow => "Subroutine stack underflow",
 | 
			
		||||
            OutOfSubStack => "Subroutine stack overflow",
 | 
			
		||||
            BuiltIn => "Built-in failed",
 | 
			
		||||
            InvalidSubEntry => "Invalid subroutine entry",
 | 
			
		||||
            Wasm => "Wasm runtime error",
 | 
			
		||||
            Internal => "Internal error",
 | 
			
		||||
            MutableCallInStaticContext => "Mutable Call In Static Context",
 | 
			
		||||
@ -108,6 +120,9 @@ impl Encodable for Error {
 | 
			
		||||
            Wasm => 8,
 | 
			
		||||
            OutOfBounds => 9,
 | 
			
		||||
            Reverted => 10,
 | 
			
		||||
            SubStackUnderflow => 11,
 | 
			
		||||
            OutOfSubStack => 12,
 | 
			
		||||
            InvalidSubEntry => 13,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        s.append_internal(&value);
 | 
			
		||||
@ -130,6 +145,9 @@ impl Decodable for Error {
 | 
			
		||||
            8 => Ok(Wasm),
 | 
			
		||||
            9 => Ok(OutOfBounds),
 | 
			
		||||
            10 => Ok(Reverted),
 | 
			
		||||
            11 => Ok(SubStackUnderflow),
 | 
			
		||||
            12 => Ok(OutOfSubStack),
 | 
			
		||||
            13 => Ok(InvalidSubEntry),
 | 
			
		||||
            _ => Err(DecoderError::Custom("Invalid error type")),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -72,6 +72,22 @@ pub enum Error {
 | 
			
		||||
        /// What was the stack limit
 | 
			
		||||
        limit: usize,
 | 
			
		||||
    },
 | 
			
		||||
    /// When there is not enough subroutine stack elements to return from
 | 
			
		||||
    SubStackUnderflow {
 | 
			
		||||
        /// How many stack elements was requested by instruction
 | 
			
		||||
        wanted: usize,
 | 
			
		||||
        /// How many elements were on stack
 | 
			
		||||
        on_stack: usize,
 | 
			
		||||
    },
 | 
			
		||||
    /// When execution would exceed defined subroutine Stack Limit
 | 
			
		||||
    OutOfSubStack {
 | 
			
		||||
        /// How many stack elements instruction wanted to pop
 | 
			
		||||
        wanted: usize,
 | 
			
		||||
        /// What was the stack limit
 | 
			
		||||
        limit: usize,
 | 
			
		||||
    },
 | 
			
		||||
    /// When the code walks into a subroutine, that is not allowed
 | 
			
		||||
    InvalidSubEntry,
 | 
			
		||||
    /// Built-in contract failed on given input
 | 
			
		||||
    BuiltIn(&'static str),
 | 
			
		||||
    /// When execution tries to modify the state in static context
 | 
			
		||||
@ -103,9 +119,11 @@ impl fmt::Display for Error {
 | 
			
		||||
        use self::Error::*;
 | 
			
		||||
        match *self {
 | 
			
		||||
            OutOfGas => write!(f, "Out of gas"),
 | 
			
		||||
            BadJumpDestination { destination } => {
 | 
			
		||||
                write!(f, "Bad jump destination {:x}", destination)
 | 
			
		||||
            }
 | 
			
		||||
            BadJumpDestination { destination } => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "Bad jump destination {:x}  (trimmed to usize)",
 | 
			
		||||
                destination
 | 
			
		||||
            ),
 | 
			
		||||
            BadInstruction { instruction } => write!(f, "Bad instruction {:x}", instruction),
 | 
			
		||||
            StackUnderflow {
 | 
			
		||||
                instruction,
 | 
			
		||||
@ -117,6 +135,13 @@ impl fmt::Display for Error {
 | 
			
		||||
                wanted,
 | 
			
		||||
                limit,
 | 
			
		||||
            } => write!(f, "Out of stack {} {}/{}", instruction, wanted, limit),
 | 
			
		||||
            SubStackUnderflow { wanted, on_stack } => {
 | 
			
		||||
                write!(f, "Subroutine stack underflow {}/{}", wanted, on_stack)
 | 
			
		||||
            }
 | 
			
		||||
            OutOfSubStack { wanted, limit } => {
 | 
			
		||||
                write!(f, "Out of subroutine stack {}/{}", wanted, limit)
 | 
			
		||||
            }
 | 
			
		||||
            InvalidSubEntry => write!(f, "Invalid subroutine entry"),
 | 
			
		||||
            BuiltIn(name) => write!(f, "Built-in failed: {}", name),
 | 
			
		||||
            Internal(ref msg) => write!(f, "Internal error: {}", msg),
 | 
			
		||||
            MutableCallInStaticContext => write!(f, "Mutable call in static context"),
 | 
			
		||||
 | 
			
		||||
@ -120,6 +120,8 @@ pub struct Schedule {
 | 
			
		||||
    pub have_chain_id: bool,
 | 
			
		||||
    /// SELFBALANCE opcode enabled.
 | 
			
		||||
    pub have_selfbalance: bool,
 | 
			
		||||
    /// BEGINSUB, JUMPSUB and RETURNSUB opcodes enabled.
 | 
			
		||||
    pub have_subs: bool,
 | 
			
		||||
    /// Kill basic accounts below this balance if touched.
 | 
			
		||||
    pub kill_dust: CleanDustMode,
 | 
			
		||||
    /// Enable EIP-1283 rules
 | 
			
		||||
@ -224,6 +226,7 @@ impl Schedule {
 | 
			
		||||
            have_bitwise_shifting: false,
 | 
			
		||||
            have_chain_id: false,
 | 
			
		||||
            have_selfbalance: false,
 | 
			
		||||
            have_subs: false,
 | 
			
		||||
            have_extcodehash: false,
 | 
			
		||||
            stack_limit: 1024,
 | 
			
		||||
            max_depth: 1024,
 | 
			
		||||
@ -303,6 +306,13 @@ impl Schedule {
 | 
			
		||||
        schedule
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Schedule for the Berlin fork of the Ethereum main net.
 | 
			
		||||
    pub fn new_berlin() -> Schedule {
 | 
			
		||||
        let mut schedule = Self::new_istanbul();
 | 
			
		||||
        schedule.have_subs = true; // EIP 2315
 | 
			
		||||
        schedule
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn new(efcd: bool, hdc: bool, tcg: usize) -> Schedule {
 | 
			
		||||
        Schedule {
 | 
			
		||||
            exceptional_failed_code_deposit: efcd,
 | 
			
		||||
@ -313,6 +323,7 @@ impl Schedule {
 | 
			
		||||
            have_bitwise_shifting: false,
 | 
			
		||||
            have_chain_id: false,
 | 
			
		||||
            have_selfbalance: false,
 | 
			
		||||
            have_subs: false,
 | 
			
		||||
            have_extcodehash: false,
 | 
			
		||||
            stack_limit: 1024,
 | 
			
		||||
            max_depth: 1024,
 | 
			
		||||
 | 
			
		||||
@ -115,6 +115,13 @@ impl FakeExt {
 | 
			
		||||
        ext
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// New fake externalities with Berlin schedule rules
 | 
			
		||||
    pub fn new_berlin() -> Self {
 | 
			
		||||
        let mut ext = FakeExt::default();
 | 
			
		||||
        ext.schedule = Schedule::new_berlin();
 | 
			
		||||
        ext
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Alter fake externalities to allow wasm
 | 
			
		||||
    pub fn with_wasm(mut self) -> Self {
 | 
			
		||||
        self.schedule.wasm = Some(Default::default());
 | 
			
		||||
 | 
			
		||||
@ -104,6 +104,8 @@ pub struct Params {
 | 
			
		||||
    /// See `CommonParams` docs.
 | 
			
		||||
    pub eip2028_transition: Option<Uint>,
 | 
			
		||||
    /// See `CommonParams` docs.
 | 
			
		||||
    pub eip2315_transition: Option<Uint>,
 | 
			
		||||
    /// See `CommonParams` docs.
 | 
			
		||||
    pub dust_protection_transition: Option<Uint>,
 | 
			
		||||
    /// See `CommonParams` docs.
 | 
			
		||||
    pub nonce_cap_increment: Option<Uint>,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user