EVM 学习笔记

| 笔记 | 2427 | 7分钟 | 区块链以太坊

本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结

老师安排的任务是EVM智能合约的加速计算,给我的论文提出了一个新的架构……

但是我还不知道原始的EVM是怎么实现的,所以先学习一下

1 Solidity

Solidity 是一种为实现智能合约而创建的高级编程语言。

最终跑在以太坊虚拟机(EVM)上的代码,就是由Solidity编译得到的。所以得先学一下Solidity是怎么写的。

基本的运算规则、语法等等查阅文档:https://learnblockchain.cn/docs/solidity

1.1 数据位置

1.1.1 分类

  • storage:用于存储状态变量,即需要长期保存的数据,生命周期与合约相同。
  • memory:用于存储临时变量,可以读写,生命周期在函数执行期间。
  • calldata:用于存储函数输入参数,是只读的,生命周期在函数执行期间。

storage 存储在区块链上,memory 在合约执行过程中的内存中,calldata 中的数据来自执行的交易本身(所以不占据额外空间而且是只读的)

通过 msg.data 可以获取完整的 calldata

1.1.2 默认位置

  • 状态变量:默认在storage中。
  • 函数内部的局部变量:值类型默认在memory中。
  • 函数参数
    • external函数的引用类型参数默认在calldata中。
    • 其他函数的引用类型参数需要显式指定memorystorage
  • 返回值:引用类型默认在memory中。

1.2 函数调用

(GPT)

内部调用和外部调用在Solidity的合约编程中都有不同的行为,在以太坊虚拟机(EVM)的实现中也有显著的区别。以下是这两种调用方式在EVM层面的差异:

1. 内部调用(Internal Call)

特点:

  • 直接调用:内部调用是通过直接跳转到目标函数的代码位置来实现的。这是因为内部调用发生在同一合约内部,EVM不需要额外的处理步骤,调用者和被调用者共享相同的上下文和存储。
  • 无消息传递:内部调用不涉及消息传递,也没有额外的Gas消耗。因为调用在相同的合约上下文中进行,所有的状态变量和内存都是直接共享的。
  • 编译优化:在内部调用时,Solidity编译器可以进行优化,如内联函数(inline functions),使得调用过程更加高效。

EVM行为:

  • EVM中的内部调用相当于一次代码跳转(jump),指令指针直接跳转到目标函数的起始位置。
  • 内部调用没有创建新的调用栈帧,保持相同的内存、堆栈和存储上下文。
  • 因为没有新的消息传递(即没有新的合约上下文被创建),内部调用的成本(Gas)相对较低。

2. 外部调用(External Call)

特点:

  • 消息传递:外部调用涉及到向另一个合约(即使是调用自身的external函数)发送消息,这会创建一个新的合约执行上下文,包括新的堆栈、内存和存储访问。
  • 新Gas限制:每次外部调用会分配一个新的Gas限制,这意味着被调用合约的执行有自己独立的Gas预算。如果这个调用消耗了超出分配的Gas,调用将失败并回滚。
  • EVM上下文切换:外部调用会在EVM中引发上下文切换,即从调用者的上下文切换到被调用合约的上下文。这种切换会涉及到堆栈的保存和恢复,以及传递调用数据。

EVM行为:

  • EVM在执行外部调用时,会创建一个新的消息调用(Message Call)。这包括构建一个新的堆栈帧,传递调用数据(包括函数选择器和参数),以及初始化一个新的存储和内存上下文。
  • 外部调用会触发EVM的CALL指令。该指令在EVM中相对昂贵,因为它涉及到一系列的操作,如设置新的Gas限制、管理新的堆栈帧、以及潜在的跨合约调用的风险管理(例如重入攻击的防范)。
  • 外部调用的结果(如返回值或状态变更)在调用结束后返回给调用者,这通常需要通过abi.decode等方法进行解码。
  • 在外部调用中,如果调用的合约抛出异常或耗尽Gas,调用者合约将会得到失败的结果,并且可以选择回滚操作。

3. 调用成本

  • 内部调用:由于没有新的消息传递和上下文切换,内部调用的成本(Gas消耗)非常低。它是通过简单的跳转和共享上下文来实现的,因此更加高效。
  • 外部调用:外部调用由于需要创建新的上下文、消息传递、处理潜在的返回数据等,成本较高。此外,外部调用可能带来安全风险,如重入攻击,这需要额外的防护措施。

4. 安全性考虑

  • 内部调用:通常被认为是安全的,因为它们在相同的合约上下文中运行,没有额外的消息传递和上下文切换。
  • 外部调用:外部调用则需要更多的安全性考虑,尤其是在涉及到第三方合约时。常见的攻击手段如重入攻击就是通过外部调用进行的。因此,开发者通常会使用checks-effects-interactions模式来防范这种风险。

总结

  • 内部调用:是一次简单的代码跳转,没有消息传递和上下文切换,效率高且Gas成本低。
  • 外部调用:涉及到新的消息传递和上下文切换,消耗更多Gas,并且需要特别注意安全性,尤其是在跨合约调用时。

理解这些差异对编写高效和安全的Solidity合约非常重要。开发者应根据实际需求选择合适的调用方式,以优化合约的性能和安全性。


一个合约最终会编译成一段字节码,函数是字节码中的一个部分。内部调用就是直接把执行的代码跳转到另一个函数;外部调用是发起了一个新的消息,创建了新的上下文。

2 EVM

看了这三篇文章:https://learnblockchain.cn/article/3779

总而言之,了解了EVM中内存存储方式,以及栈式指令的执行过程。