EVM 学习笔记
本文属于学习笔记,内容可能有误、可能不全面,仅代表个人在学习这一特性时的理解和总结
老师安排的任务是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中。- 其他函数的引用类型参数需要显式指定
memory或storage。
- 返回值:引用类型默认在
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中内存存储方式,以及栈式指令的执行过程。