REVM源码阅读-流程(4)

ExecuteExecute阶段是整个流程中最重要的Execute所有交易将在这里执行直接跳到源码实现的部分参数二init_and_floor_gas是Validate阶段的返回值是交易的“初始化gas消耗+地板gas限制”gas_limit=tx设置的Gas

Execute

Execute 阶段是整个流程中最重要的 Execute\ 所有交易将在这里执行

直接跳到源码实现的部分\ 参数二 init_and_floor_gasValidate 阶段的返回值\ 是交易的“初始化 gas 消耗 + 地板 gas 限制”\ gas_limit = tx设置的GasLimit- init_and_floor_gas.initial_gas

// crates/handler/src/handler.rs
    #[inline]
    fn execution(
        &mut self,
        evm: &mut Self::Evm,
        init_and_floor_gas: &InitialAndFloorGas,
    ) -> Result<FrameResult, Self::Error> {
        let gas_limit = evm.ctx().tx().gas_limit() - init_and_floor_gas.initial_gas;
        // Create first frame action
        let first_frame_input = self.first_frame_input(evm, gas_limit)?;

        // Run execution loop
        let mut frame_result = self.run_exec_loop(evm, first_frame_input)?;

        // Handle last frame result
        self.last_frame_result(evm, &mut frame_result)?;
        Ok(frame_result)
    }

1.first_frame_input

使用交易参数、gas限制和配置创建初始帧输入。\ 直接跳到代码实现部分

// crates/handler/src/handler.rs
#[inline]
    fn first_frame_input(
        &mut self,
        evm: &mut Self::Evm,
        gas_limit: u64,
    ) -> Result<FrameInit, Self::Error> {
        let ctx = evm.ctx_mut();
        let mut memory = SharedMemory::new_with_buffer(ctx.local().shared_memory_buffer().clone());
        memory.set_memory_limit(ctx.cfg().memory_limit());

        let (tx, journal) = ctx.tx_journal_mut();
        let bytecode = if let Some(&to) = tx.kind().to() {
            let account = &journal.load_account_with_code(to)?.info;

            if let Some(Bytecode::Eip7702(eip7702_bytecode)) = &account.code {
                let delegated_address = eip7702_bytecode.delegated_address;
                let account = &journal.load_account_with_code(delegated_address)?.info;
                Some((
                    account.code.clone().unwrap_or_default(),
                    account.code_hash(),
                ))
            } else {
                Some((
                    account.code.clone().unwrap_or_default(),
                    account.code_hash(),
                ))
            }
        } else {
            None
        };

        Ok(FrameInit {
            depth: 0,
            memory,
            frame_input: execution::create_init_frame(tx, bytecode, gas_limit),
        })
    }

ctx.local() 返回的是 LocalContext, 作用是为当前交易(tx)提供一个“本地、临时的、共享的上下文缓存”,用来在整个交易执行过程中(包括多层调用)复用一些资源和状态,避免重复分配和拷贝,提高性能,同时处理一些特殊场景(如 initcode 交易、precompile 错误传递.

SharedMemory::new_with_buffer 就是在外面加一层封装,把 Rc<RefCell<Vec<u8>>>,也就是ctx.local().shared_memory_buffer() 包装成一个更安全、更易用、支持预分配的共享内存对象,目的是让交易执行中的内存拷贝和切片操作更高效、更不容易出错,同时保持整个 tx 共用一个缓冲区的性能优势。

SharedMemoryLocalContext 我也还没细看,上面是 Grok 解释的.\ 先走完流程,后面再在后面单独的章节介绍

tx.kind 之前介绍,分为 Create 和 Call.\ 创建合约属于 Create, 调用合约 和 Transfer 属于Call\ EIP-4844和EIP-7702 只能 Call, 不能 Create\ 这里获取tx的to

if let Some(&to) = tx.kind().to()

获取to的code,判断是不是 EIP7702\ 如果是的话找出被委托地址再加载被委托地址的code

if let Some(Bytecode::Eip7702(eip7702_bytecode)) = &account.code {
 let delegated_address = eip7702_bytecode.delegated_address;
 let account = &journal.load_account_with_code(delegated_address)?.info;
 Some((
  account.code.clone().unwrap_or_default(),
  account.code_hash(),
 ))
} 

如果不是 EIP7702 , 说明是合约,直接获取合约的code

else {
 Some((
  account.code.clone().unwrap_or_default(),
  account.code_hash(),
 ))
}

这里先介绍下Frame\ Frame 是调用帧(Call Frame),在EVM执行过程每次合约间的调用(不是函数调用),例如CALL、DELEGATECALL 、 STATICCALL 、CREATE,都会生成一个Frame,里面包含本次调用的上下文.\ 后面还会有一个 frame_stack, 负责保存所有生成的frame

first_frame_input 的返回结果\ depth 指的是frame调用深度,例如A合约调用B合约,再调用C合约,C合约的frame depth就是2

FrameInit {
 depth: 0,
 memory,
 frame_input: execution::create_init_frame(tx, bytecode, gas_limit),
}

create_init_frame 也没有什么要特别讲的,返回一个FrameInput类型

// crates/handler/src/execution.rs
#[inline]
pub fn create_init_frame(
    tx: &impl Transaction,
    bytecode: Option<(Bytecode, B256)>,
    gas_limit: u64,
) -> FrameInput {
    let input = tx.input().clone();

    match tx.kind() {
        TxKind::Call(target_address) => {
            let known_bytecode = bytecode.map(|(code, hash)| (hash, code));
            FrameInput::Call(Box::new(CallInputs {
                input: CallInput::Bytes(input),
                gas_limit,
                target_address,
                bytecode_address: target_address,
                known_bytecode,
                caller: tx.caller(),
                value: CallValue::Transfer(tx.value()),
                scheme: CallScheme::Call,
                is_static: false,
                return_memory_offset: 0..0,
            }))
        }
        TxKind::Create => FrameInput::Create(Box::new(CreateInputs::new(
            tx.caller(),
            CreateScheme::Create,
            tx.value(),
            input,
            gas_limit,
        ))),
    }
}

2. run_exec_loop

注释中提到的 run_exec_loop 的功能:\ 执行主帧处理循环,此循环管理帧堆栈,处理每一帧直至执行完成。\ 每次迭代:

  1. 调用当前帧
  2. 处理返回的帧输入或结果
  3. 根据需要创建新帧或传播结果。

直接看代码吧\ 输入参数是 EVM 和 上一步 first_frame_input 返回的参数 FrameInit

// crates/handler/src/handler.rs
#[inline]
    fn run_exec_loop(
        &mut self,
        evm: &mut Self::Evm,
        first_frame_input: <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameInit,
    ) -> Result<FrameResult, Self::Error> {
        let res = evm.frame_init(first_frame_input)?;

        if let ItemOrResult::Result(frame_result) = res {
            return Ok(frame_result);
        }

        loop {
            let call_or_result = evm.frame_run()?;

            let result = match call_or_result {
                ItemOrResult::Item(init) => {
                    match evm.frame_init(init)? {
                        ItemOrResult::Item(_) => {
                            continue;
                        }
                        // Do not pop the frame since no new frame was created
                        ItemOrResult::Result(result) => result,
                    }
                }
                ItemOrResult::Result(result) => result,
            };

            if let Some(result) = evm.frame_return_result(result)? {
                return Ok(result);
            }
        }
    }

先看 frame_init 的实现

frame_init

// crates/handler/src/evm.rs
#[inline]
    fn frame_init(
        &mut self,
        frame_input: <Self::Frame as FrameTr>::FrameInit,
    ) -> Result<FrameInitResult<'_, Self::Frame>, ContextDbError<CTX>> {
        let is_first_init = self.frame_stack.index().is_none();
        let new_frame = if is_first_init {
            self.frame_stack.start_init()
        } else {
            self.frame_stack.get_next()
        };

        let ctx = &mut self.ctx;
        let precompiles = &mut self.precompiles;
        let res = Self::Frame::init_with_context(
            new_frame,
            ctx,
            precompiles,
            frame_input,
            self.instruction.gas_params(),
        )?;

        Ok(res.map_frame(|token| {
            if is_first_init {
                unsafe { self.frame_stack.end_init(token) };
            } else {
                unsafe { self.frame_stack.push(token) };
            }
            self.frame_stack.get()
        }))
    }

根据 frame_stack.index 判断是否是第一次初始化.\ 分别调用 start_initget_next

  • start_init

    • 判断如果栈是空,则预分配8个元素的栈空间
  • get_next

    • 如果stack满了,再次预分配8个元素的栈空间
  • out_frame_at 返回的是 传入参数 位置的元素引用

// crates/context/interface/src/local.rs
#[inline]
pub fn start_init(&mut self) -> OutFrame<'_, T> {
 self.index = None;
 if self.stack.is_empty() {
  self.stack.reserve(8);
 }
 self.out_frame_at(0)
}
#[inline]
pub fn get_next(&mut self) -> OutFrame<'_, T> {
 if self.index.unwrap() + 1 == self.stack.capacity() {
  // allocate 8 more items
  self.stack.reserve(8);
 }
 self.out_frame_at(self.index.unwrap() + 1)
}

继续往下,主要关注 init_with_context

init_with_context

init_with_context的 作用是使用给定的上下文初始化帧并进行预编译。\ 跳转到到实现部分.先说明一下传入的参数:

  • OutFrame 上一步 start_init 和 get_next 创建返回的空Frame
  • precompiles 预编译的合约
  • frame_init 前面调用 first_frame_input 返回的
  • gas_params instruction.gas_params(), 保存的是每个opcode的基础gas消耗,只是静态部分,如果在执行过程中涉及类似内存扩展之类的,回会额外增加gas
// crates/handler/src/frame.rs
pub fn init_with_context<
        CTX: ContextTr,
        PRECOMPILES: PrecompileProvider<CTX, Output = InterpreterResult>,
    >(
        this: OutFrame<'_, Self>,
        ctx: &mut CTX,
        precompiles: &mut PRECOMPILES,
        frame_init: FrameInit,
        gas_params: GasParams,
    ) -> Result<
        ItemOrResult<FrameToken, FrameResult>,
        ContextError<<<CTX as ContextTr>::Db as Database>::Error>,
    > {
        // TODO cleanup inner make functions
        let FrameInit {
            depth,
            memory,
            frame_input,
        } = frame_init;

        match frame_input {
            FrameInput::Call(inputs) => {
                Self::make_call_frame(this, ctx, precompiles, depth, memory, inputs, gas_params)
            }
            FrameInput::Create(inputs) => {
                Self::make_create_frame(this, ctx, depth, memory, inputs, gas_params)
            }
            FrameInput::Empty => unreachable!(),
        }
    }
}

根据 frame_input 类型来生成不同的Frame, frame_inputfirst_frame_input 返回的结果里的

make_call_frame

函数太长,这次就不贴了\ 先讲下函数参数,只讲 init_with_context 没有的部分

  • depth 当前处于第几层合约调用
  • memory first_frame_input 中提到过的共享上下文缓存
  • inputs 前面返回的 frame_input

继续看函数实现\ 这里是创建一个闭包用于返回错误结果

// crates/handler/src/frame.rs -> make_call_frame
let return_result = |instruction_result: InstructionResult| {
 Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
  result: InterpreterResult {
   result: instruction_result,
   gas,
   output: Bytes::new(),
  },
  memory_offset: inputs.return_memory_offset.clone(),
  was_precompile_called: false,
  precompile_call_logs: Vec::new(),
 })))
};

创建检查点,用于后面执行出错的时候返回到检查点.

let checkpoint = ctx.journal_mut().checkpoint();

跳进去看下checkpoint的实现\ 创建一个JournalCheckpoint并返回而已\ 后面我们再讲 Revert 的原理\ 这里的 depth 和之前说到的 depth 是一个东西,都是合约间到了第几层调用.\ 不过这里 depth 是一个全局状态

// crates/context/src/journal/inner.rs
#[inline]
pub fn checkpoint(&mut self) -> JournalCheckpoint {
 let checkpoint = JournalCheckpoint {
  log_i: self.logs.len(),
  journal_i: self.journal.len(),
 };
 self.depth += 1;
 checkpoint
}

接着往下,这里是如果tx中有value, 则提前扣除 from 的 Balance, 增加 to 的 Balance

// crates/handler/src/frame.rs -> make_call_frame
if let CallValue::Transfer(value) = inputs.value {
 // Transfer value from caller to called account
 // Target will get touched even if balance transferred is zero.
 if let Some(i) =
  ctx.journal_mut()
   .transfer_loaded(inputs.caller, inputs.target_address, value)
 {
  ctx.journal_mut().checkpoint_revert(checkpoint);
  return return_result(i.into());
 }
}

进去看下 transfer_loaded 的实现,直接在注释中说明,就不一一说了

// crates/context/src/journal/inner.rs
#[inline]
    pub fn transfer_loaded(
        &mut self,
        from: Address,
        to: Address,
        balance: U256,
    ) -> Option<TransferError> {
  // 给自己转账的情况
        if from == to {
            let from_balance = self.state.get_mut(&to).unwrap().info.balance;
            // Check if from balance is enough to transfer the balance.
            if balance > from_balance {
                return Some(TransferError::OutOfFunds);
            }
            return None;
        }
  // tx.value为0的情况
  // touch_account 标记 account 为touched,方便在交易结束后处理账户状态
        if balance.is_zero() {
            Self::touch_account(&mut self.journal, to, self.state.get_mut(&to).unwrap());
            return None;
        }
        // 扣除 from 的 balance
        let from_account = self.state.get_mut(&from).unwrap();
        Self::touch_account(&mut self.journal, from, from_account);
        let from_balance = &mut from_account.info.balance;
        let Some(from_balance_decr) = from_balance.checked_sub(balance) else {
            return Some(TransferError::OutOfFunds);
        };
        *from_balance = from_balance_decr;

        // 增加to的balance
        let to_account = self.state.get_mut(&to).unwrap();
        Self::touch_account(&mut self.journal, to, to_account);
        let to_balance = &mut to_account.info.balance;
        let Some(to_balance_incr) = to_balance.checked_add(balance) else {
            // Overflow of U256 balance is not possible to happen on mainnet. We don't bother to return funds from from_acc.
            return Some(TransferError::OverflowPayment);
        };
        *to_balance = to_balance_incr;

        // push journal entry,每次更改都push,用于后面revert
        self.journal
            .push(ENTRY::balance_transfer(from, to, balance));

        None
    }

继续看下 checkpoint_revert 的实现\ revert 的原理是每次涉及到更改都保存到journal中,保存的是差异更改,不是状态.\ 就像 git ,保存的是每次的差异,而不是每次都保存一份副本.\ revert 的时候从后往前撤销更改到 checkpoint .

这里的逻辑就不一一解释, 直接在注释中解释了.

// crates/context/src/journal/inner.rs
#[inline]
    pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
        let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
        let state = &mut self.state;
        let transient_storage = &mut self.transient_storage;
        // 检查点是在合约开始的时候生成的,
        // 回滚也就是revert这次合约调用,所以depth-1
        self.depth = self.depth.saturating_sub(1);
        // 截断日志为checkpoint时保存的log 位置.
        // 也就是删除checkpoint之后的所有日志
        self.logs.truncate(checkpoint.log_i);
        // self.journal.drain(checkpoint.journal_i..) 是删除checkpoint之后
        // 的所有 entry,并以迭代器的形式返回这些元素.再rev逆序
        // 也就是从后向前一步步恢复
        if checkpoint.journal_i < self.journal.len() {
            self.journal
                .drain(checkpoint.journal_i..)
                .rev()
                .for_each(|entry| {
                    entry.revert(state, Some(transient_storage), is_spurious_dragon_enabled);
                });
        }
    }

关键的部分是 entry.revert, 里面针对每种情况进行了处理.\ 代码太长,不贴了,感兴趣的话自己去看下\ 位置在 crates/context/interface/src/journaled_state/entry.rs -> revert

继续 make_call_frame 的逻辑\ 这里是判断 tx.to 是否是 precompile 的地址.\ 如果是直接走 precompile 的逻辑,而不用走后面的解释器逻辑.节省时间、提升性能\ 这里就不深入** precompiles.run** 了

// crates/handler/src/frame.rs -> make_call_frame
let interpreter_input = InputsImpl {
 target_address: inputs.target_address,
 caller_address: inputs.caller,
 bytecode_address: Some(inputs.bytecode_address),
 input: inputs.input.clone(),
 call_value: inputs.value.get(),
};
let is_static = inputs.is_static;
let gas_limit = inputs.gas_limit;

if let Some(result) = precompiles.run(ctx, &inputs).map_err(ERROR::from_string)? {
 let mut logs = Vec::new();
 if result.result.is_ok() {
  ctx.journal_mut().checkpoint_commit();
 } else {
  // 如果precompile 运行错误,则revert
  logs = ctx.journal_mut().logs()[checkpoint.log_i..].to_vec();
  ctx.journal_mut().checkpoint_revert(checkpoint);
 }
 return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome {
  result,
  memory_offset: inputs.return_memory_offset.clone(),
  was_precompile_called: true,
  precompile_call_logs: logs,
 })));
}

继续往下\ 这里处理的是一种特殊情况的分支.\ 是在 fork 主网、模拟交易时,调用者可能已经从 RPC 或缓存里拿到了 bytecode,直接传入 known_bytecode 更快,避免每次都走 load_account_with_code\ 如果known_bytecode不存在则从数据库中加载\ 如果是 bytecode 是空,说明to是EOA账户,直接返回

// crates/handler/src/frame.rs -> make_call_frame
let (bytecode, bytecode_hash) = if let Some((hash, code)) = inputs.known_bytecode.clone() {
 (code, hash)
} else {
 let account = ctx
  .journal_mut()
  .load_account_with_code(inputs.bytecode_address)?;
 (
  account.info.code.clone().unwrap_or_default(),
  account.info.code_hash,
 )
};

// 
if bytecode.is_empty() {
 ctx.journal_mut().checkpoint_commit();
 return return_result(InstructionResult::Stop);
}

继续往下\ 初始化并启动新CallFrame\ get(EthFrame::invalid).clear找出stack中非法的frame并重置为新的CallFrame\ Ok(ItemOrResult::Item(this.consume())) 指示frame已经初始化

// crates/handler/src/frame.rs -> make_call_frame
this.get(EthFrame::invalid).clear(
 FrameData::Call(CallFrame {
  return_memory_range: inputs.return_memory_offset.clone(),
 }),
 FrameInput::Call(inputs),
 depth,
 memory,
 ExtBytecode::new_with_hash(bytecode, bytecode_hash),
 interpreter_input,
 is_static,
 ctx.cfg().spec().into(),
 gas_limit,
 checkpoint,
 gas_params,
);
Ok(ItemOrResult::Item(this.consume()))
make_create_frame

函数参数和上面一样:

  • depth 当前处于第几层合约调用
  • memory first_frame_input 中提到过的共享上下文缓存
  • inputs 前面返回的 frame_input

里面好多部分都和 make_call_frame 差不多,我们主要讲解不一样的\ 这里是判断用的 Create 还是 Create2.\ 两者计算地址的方式不同,生成的地址也不一样.\ CREATE2 的生成跟 nonce 无关,所以可以每次生成一样的地址.

  • CREATE\ address = keccak256(rlp([sender_address, sender_nonce]))的后 20 字节
  • CREATE2\ keccak256(0xff ++ sender_address ++ salt ++ keccak256(initcode)) 的后 20 字节

这里不深入细节了,感兴趣的自己去看下

// crates/handler/src/frame.rs -> make_create_frame
let mut init_code_hash = None;
let created_address = match inputs.scheme() {
 CreateScheme::Create => inputs.caller().create(old_nonce),
 CreateScheme::Create2 { salt } => {
  let init_code_hash = *init_code_hash.insert(keccak256(inputs.init_code()));
  inputs.caller().create2(salt.to_be_bytes(), init_code_hash)
 }
 CreateScheme::Custom { address } => address,
};

继续往下看\ 直接代码里解释了

// crates/handler/src/frame.rs -> make_create_frame
// 把交易的 init_code(初始化代码)包装成 ExtBytecode
let bytecode = ExtBytecode::new_with_optional_hash(
 Bytecode::new_legacy(inputs.init_code().clone()),
 init_code_hash,
);

// revm 解释器(Interpreter)的输入结构,描述“当前执行上下文”。
let interpreter_input = InputsImpl {
 target_address: created_address,
 caller_address: inputs.caller(),
 bytecode_address: None,
 input: CallInput::Bytes(Bytes::new()),
 call_value: inputs.value(),
};
let gas_limit = inputs.gas_limit();

// 初始化并启动新 CreateFrame
// get(EthFrame::invalid).clear找出stack中非法的frame并重置为新的CreateFrame
this.get(EthFrame::invalid).clear(
 FrameData::Create(CreateFrame { created_address }),
 FrameInput::Create(inputs),
 depth,
 memory,
 bytecode,
 interpreter_input,
 false,
 spec,
 gas_limit,
 checkpoint,
 gas_params,
);

继续回到 crates/handler/src/evm.rs -> frame_init

token 里面主要是bool值,指示知否初始化frame成功

Ok(res.map_frame(|token| {
 if is_first_init {
  unsafe { self.frame_stack.end_init(token) };
 } else {
  unsafe { self.frame_stack.push(token) };
 }
 self.frame_stack.get()
}))

还记得前面判断 is_first_init 是通过 self.frame_stack.index().is_none() 吗

let is_first_init = self.frame_stack.index().is_none()

我们进去看下 end_init\ 可以看到, end_init 会将 self.index 设置为 0 ,下次再执行 is_first_init 就会为 false

// crates/context/interface/src/local.rs
#[inline]
pub unsafe fn end_init(&mut self, token: FrameToken) {
 token.assert();
 if self.stack.is_empty() {
  unsafe { self.stack.set_len(1) };
 }
 self.index = Some(0);
}

我们再去看下 push

// crates/context/interface/src/local.rs
#[inline]
pub unsafe fn push(&mut self, token: FrameToken) {
 token.assert();
 // 这里的index 和前面的depth效果很像
 let index = self.index.as_mut().unwrap();
 *index += 1;
 // capacity of stack is incremented in `get_next`
 debug_assert!(
  *index < self.stack.capacity(),
  "Stack capacity is not enough for index"
 );
 // 如果index是最后一个则增加长度
 if *index == self.stack.len() {
  unsafe { self.stack.set_len(self.stack.len() + 1) };
 }
}

继续回到 crates/handler/src/handler.rs -> run_exec_loop

// to是eoa账户、预编译合约、或者其他失败情况
if let ItemOrResult::Result(frame_result) = res {
 return Ok(frame_result);
}

继续往下,这里先讲下整体流程,后面再深入 frame_runframe_return_result

loop {
 // 尝试运行当前帧(执行一条或多条 opcode)
 let call_or_result = evm.frame_run()?;

 // 根据 frame_run 的返回,判断是“需要初始化新帧”还是“直接得到结果”
 let result = match call_or_result {
  ItemOrResult::Item(init) => {
   match evm.frame_init(init)? {
    // 新帧创建成功,继续循环执行这个新帧
    ItemOrResult::Item(_) => {
     continue;
    }
    // 初始化新帧时失败了
    ItemOrResult::Result(result) => result,
   }
  }
  ItemOrResult::Result(result) => result,
 };

 // 处理帧的返回结果(可能结束整个执行,也可能继续)
 if let Some(result) = evm.frame_return_result(result)? {
  return Ok(result);
 }
 // 如果 frame_return_result 返回 None,说明还需要继续执行(比如还有上层帧)
}

深入 frame_run

// crates/handler/src/evm.rs
#[inline]
    fn frame_run(&mut self) -> Result<FrameInitOrResult<Self::Frame>, ContextDbError<CTX>> {
     // 从frame_stack弹出当前frame
        let frame = self.frame_stack.get();
        let context = &mut self.ctx;
        // instructions保存的是opcode和opcode对应的函数指针
        let instructions = &mut self.instruction;

        let action = frame
            .interpreter
            .run_plain(instructions.instruction_table(), context);

        frame.process_next_action(context, action).inspect(|i| {
            if i.is_result() {
                frame.set_finished(true);
            }
        })
    }

继续深入 run_plain\ 逻辑很简单.就是循环获取self.bytecode,如果不为空则执行 step

// crates/interpreter/src/interpreter.rs
#[inline]
pub fn run_plain<H: Host + ?Sized>(
 &mut self,
 instruction_table: &InstructionTable<IW, H>,
 host: &mut H,
) -> InterpreterAction {
 while self.bytecode.is_not_end() {
  self.step(instruction_table, host);
 }
 self.take_next_action()
}

继续深入 step\ 流程都蛮清晰的\ 因为难点不是在流程这边,而是在opcode.\ 在这系列文章中我们先不深入opcode,后面会有专门的系列文章

#[inline]
    pub fn step<H: Host + ?Sized>(
        &mut self,
        instruction_table: &InstructionTable<IW, H>,
        host: &mut H,
    ) {
        // 获取当前opcode
        let opcode = self.bytecode.opcode();
        // 跳到下一个opcode
        self.bytecode.relative_jump(1);
 
  // 获取opcode对应的函数
        let instruction = unsafe { instruction_table.get_unchecked(opcode as usize) };

  // 获取当前指令的静态gas
  // 判断当前gas够不够,如果gas不够则返回gas不够的错误
        if self.gas.record_cost_unsafe(instruction.static_gas()) {
            return self.halt_oog();
        }
        let context = InstructionContext {
            interpreter: self,
            host,
        };
        // 执行
        instruction.execute(context);
    }

take_next_action

在 frame 运行过程中(执行opcode),会生成一些action.\ 例如 CALL、CREATE、RETURN,在前面提到过合约之间调用会生成新的 frame.\ 生成 action 后会 break 循环(while self.bytecode.is_not_end())\ take_next_action 就是将 action 取出来交给外层的 frame_run 进行处理.

// crates/interpreter/src/interpreter.rs
#[inline]
pub fn take_next_action(&mut self) -> InterpreterAction {
 self.bytecode.reset_action();
 // Return next action if it is some.
 let action = core::mem::take(self.bytecode.action()).expect("Interpreter to set action");
 action
}

继续回到 frame_run

frame.process_next_action(context, action).inspect(|i| {
 if i.is_result() {
  frame.set_finished(true);
 }
})

跳转到具体实现

// crates/handler/src/frame.rs
pub fn process_next_action<
        CTX: ContextTr,
        ERROR: From<ContextTrDbError<CTX>> + FromStringError,
    >(
        &mut self,
        context: &mut CTX,
        next_action: InterpreterAction,
    ) -> Result<FrameInitOrResult<Self>, ERROR> {
        let spec = context.cfg().spec().into();
  // 在这里处理 take_next_action 返回的action
        let mut interpreter_result = match next_action {
         // 这里就是返回创建新的frame结果
         // 实际处理不是在这里,而是外层handler的run_exec_loop,重走流程
            InterpreterAction::NewFrame(frame_input) => {
                let depth = self.depth + 1;
                return Ok(ItemOrResult::Item(FrameInit {
                    frame_input,
                    depth,
                    memory: self.interpreter.memory.new_child_context(),
                }));
            }
            // frame 结束(包含正常结束和不正常结束)
            InterpreterAction::Return(result) => result,
        };

        // 处理frame的返回,这里很像tx.kind
        // 只有call和create两种结果
        let result = match &self.data {
            FrameData::Call(frame) => {
    // 结果正常
                if interpreter_result.result.is_ok() {
                    context.journal_mut().checkpoint_commit();
                } else {
                 // frame revert的情况
                    context.journal_mut().checkpoint_revert(self.checkpoint);
                }
                ItemOrResult::Result(FrameResult::Call(CallOutcome::new(
                    interpreter_result,
                    frame.return_memory_range.clone(),
                )))
            }
            FrameData::Create(frame) => {
                let max_code_size = context.cfg().max_code_size();
                let is_eip3541_disabled = context.cfg().is_eip3541_disabled();
                return_create(
                    context.journal_mut(),
                    self.checkpoint,
                    &mut interpreter_result,
                    frame.created_address,
                    max_code_size,
                    is_eip3541_disabled,
                    spec,
                );

                ItemOrResult::Result(FrameResult::Create(CreateOutcome::new(
                    interpreter_result,
                    Some(frame.created_address),
                )))
            }
        };

        Ok(result)
    }

跳转回到 crates/handler/src/handler.rs -> run_exec_loop

if let Some(result) = evm.frame_return_result(result)? {
 return Ok(result);
}

进去看下实现\ 直接在代码里进行解释了

// crates/handler/src/evm.rs
#[inline]
    fn frame_return_result(
        &mut self,
        result: <Self::Frame as FrameTr>::FrameResult,
    ) -> Result<Option<<Self::Frame as FrameTr>::FrameResult>, ContextDbError<Self::Context>> {
     // 判断当前frame是否完成结束
     // 如果是则pop,此时栈顶变成父frame
        if self.frame_stack.get().is_finished() {
            self.frame_stack.pop();
        }
        // 前面如果pop后,index为none,说明到了最外层
        // tx执行结束了
        if self.frame_stack.index().is_none() {
            return Ok(Some(result));
        }
        // 刚才pop之后,栈顶是父frame
        // 这里是将当前frame 的return_result传递返回给父frame
        self.frame_stack
            .get()
            .return_result::<_, ContextDbError<Self::Context>>(&mut self.ctx, result)?;
        Ok(None)
    }

继续跳转回到最外面的 crates/handler/src/handler.rs > execution

last_frame_result

这里是对 Gas 进行退还处理

#[inline]
    fn last_frame_result(
        &mut self,
        evm: &mut Self::Evm,
        frame_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
    ) -> Result<(), Self::Error> {
        let instruction_result = frame_result.interpreter_result().result;
        // 当前frame的gas结构体,包含spent、remaining、refunded
        let gas = frame_result.gas_mut();
        // gas 的剩余,后面要返还给caller
        let remaining = gas.remaining();
        // 可退还的gas, SSTORE 清零、SELFDESTRUCT 等产生的 refund
        let refunded = gas.refunded();
  // 先预扣所有的gas,后面再计算退还
        *gas = Gas::new_spent(evm.ctx().tx().gas_limit());
  // 无论执行成功或者失败都退还执行剩余的gas
        if instruction_result.is_ok_or_revert() {
            gas.erase_cost(remaining);
        }
  // 只有执行成功才退还refund,如果是revert则不退还
        if instruction_result.is_ok() {
            gas.record_refund(refunded);
        }
        Ok(())
    }
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论
唐好酸
唐好酸
江湖只有他的大名,没有他的介绍。