文章目录
- 1. EVM 概述
- 以太坊虚拟机(Ethereum Virtual Machine,EVM)的作用
- EVM 如何执行智能合约
- 账户类型
- 2. EVM 体系结构
- 栈(Stack)
- 内存(Memory)
- 存储(Storage)
- Gas 机制
- 3. EVM 运行流程
- 4. 智能合约执行流程:部署、调用、状态变化
- 5. EVM 指令集(Opcodes)
- 算术运算指令
- 逻辑运算指令
- 6. Gas 计算
1. EVM 概述
以太坊虚拟机(Ethereum Virtual Machine,EVM)是以太坊区块链的核心计算环境,负责执行智能合约和处理交易。EVM 让所有以太坊节点能够达成共识,并在去中心化网络中保持一致的计算结果。
以太坊虚拟机(Ethereum Virtual Machine,EVM)的作用
1.所有以太坊智能合约代码都在 EVM 内部运行,不依赖外部环境,确保代码执行的确定性。
2.EVM 运行在沙箱环境中,合约代码无法直接访问外部系统资源,确保安全性。
3.EVM 通过存储机制(Storage & Memory)记录智能合约的数据,所有交易最终会影响全局状态。
4.EVM 通过 Gas 费用防止恶意计算资源滥用,提高网络稳定性。
EVM 如何执行智能合约
1.EOA(外部拥有账户)发起交易,交易可以:
- 向另一个 EOA 或合约账户发送 ETH。
- 向合约账户发送数据,执行智能合约中的某个函数。
2.交易进入等待队列(内存池,Mempool),由矿工(PoW)或验证者(PoS)选择并打包进区块。
3.交易被矿工/验证者执行
- 交易执行时,EVM 读取合约代码,并根据操作码(Opcode)逐步执行。
- 操作码可能涉及计算、存储变更、资金转移等。
- 每个操作码都需要消耗 Gas,如果 Gas 不足,交易失败。
4.交易完成 & 状态更新
- 交易执行完毕后,EVM 将最终结果提交到全局状态数据库,并存入区块链。
- 交易的执行结果是不可逆的,区块链上的数据无法修改。
账户类型
以太坊有两种账户类型,EOA(外部拥有账户)与合约账户。
1.EOA(外部拥有账户,Externally Owned Account)
由用户控制,需要私钥进行签名。
可以发送交易(ETH 转账、调用智能合约)。
无法执行合约代码。
2.CA(合约账户,Contract Account)
由智能合约代码控制,存储在区块链上。
不能主动发起交易,只能被 EOA 或其他合约调用。
代码执行后,可以修改状态(如存储变量)或调用其他合约。
示例:EOA 发送 ETH
用户 A(EOA)向用户 B(EOA)发送 1 ETH。交易签名后广播到以太坊网络,矿工打包后执行转账。
示例:EOA 调用智能合约
用户 A(EOA)调用一个 DEX 智能合约,要求兑换 ETH → USDT。交易发送到合约账户,合约代码执行兑换逻辑,完成交易。
示例:合约之间的交互
Uniswap 智能合约可以调用另一个合约(如 ERC-20 代币合约)来完成代币交易。
2. EVM 体系结构
在 EVM(以太坊虚拟机)的体系结构中,栈、内存、存储和 Gas 机制是四个非常关键的组件,它们共同影响了合约执行的效率和成本。
栈(Stack)
EVM 使用一个 256-bit 宽度的栈,栈的深度最大为 1024。栈是一种后进先出(LIFO)的数据结构,用于存储临时的计算结果。
栈用于存储合约执行过程中计算的中间结果。操作数和结果的进出都是通过栈完成的。每当执行一条指令时,它可能会从栈中弹出操作数并将结果推入栈中。
内存(Memory)
EVM 中的内存是一个临时的存储区域,它的数据不会在交易执行后保留。
内存用于存储合约执行过程中需要的临时数据,比如数组、字符串等,通常用于函数调用的中间数据存储。内存的内容在交易完成后会被清除。
特点
1.内存的大小是动态变化的,可以根据需求扩展。
2.内存访问速度相对较快,但需要消耗 Gas。
3.由于它是临时的,所以不会像存储那样在交易完成后保留。
存储(Storage)
存储是合约的持久化存储区域,所有合约的状态变量都存储在这里。
存储用于保存合约的数据,并且存储的内容在交易结束后不会被清除。这意味着合约的状态变量(例如账户余额、合同设定等)会被保留,并在之后的交易中继续使用。
特点
存储的访问速度相对较慢。
存储是持久的,数据不会因为交易结束而清除。
每次对存储的修改都需要消耗 Gas,修改存储比修改内存更加昂贵。
Gas 机制
Gas 是 EVM 中用于衡量计算和存储操作成本的单位。它确保智能合约的执行不会因为无限循环或资源消耗过度而影响网络的稳定性。
每个操作(无论是计算、存储、还是其他合约交互)都会消耗一定的 Gas。交易发起者必须提供足够的 Gas 来执行智能合约,否则合约执行将中止。
特点
1.每个交易都有一个最大 Gas 限制,确保交易在消耗预定 Gas 后结束。
2.每个 Gas 单位都有一个价格,由交易发起者设定。
3.不同的操作消耗不同量的 Gas。例如,简单的数学运算消耗少量 Gas,而存储操作则消耗大量 Gas。
3. EVM 运行流程
1.用户或智能合约发起交易,并使用私钥对交易进行签名。交易包括目标地址、金额、Gas 限额、输入数据等信息。签名后的交易被广播到以太坊网络中的所有节点,进入内存池等待处理。
2.每个节点会验证交易的有效性,具体验证步骤包括:
- 签名验证,确保交易是由合法账户发起的。
- 确认发送方账户余额足够支付交易金额和 Gas 费用。
- 确保交易的 Gas 限额足够执行。
- 确保交易的 nonce(交易序号)正确,防止重放攻击。
3.经过验证的交易被矿工或验证节点打包到新区块。矿工根据交易的 Gas 价格来决定交易的优先级。每个区块的大小有限,因此矿工会优先选择 Gas 价格更高的交易。
4.当交易中的目标是智能合约时,EVM 会执行该交易。
- EVM 从区块链中读取并加载目标合约的字节码,按照合约的字节码逐条执行指令,指令包括算术操作、条件跳转、存储操作等。
- 栈用于存储临时计算结果,每次执行指令时会从栈中弹出或推入数据。
- 内存用于存储临时数据,如函数调用中的变量等,交易结束后会被清空。
- 存储用于存储合约的持久化状态(例如账户余额、合约设置等),更新时会消耗 Gas。
5.每执行一条指令,EVM 会消耗一定量的 Gas。Gas 消耗的数量取决于操作的复杂性(例如,存储操作消耗的 Gas 比简单的算术运算要多)。如果 Gas 不足,交易会被回滚,合约执行不会生效,所有修改都会撤销。
6.如果合约的状态发生了变化(如修改了状态变量),EVM 会更新存储中的数据。如果合约触发了事件,EVM 会记录这些事件日志,这些日志可以在区块链外部查询到。
7.新区块被成功挖掘并添加到区块链中,交易被认为已被确认。
8.区块通过网络同步到其他节点,所有节点都会更新自己的本地区块链,并执行新区块中的交易。
9.用户和其他应用可以通过区块浏览器或其他工具查询交易的结果。
4. 智能合约执行流程:部署、调用、状态变化
1.合约部署是将合约代码(字节码)上传到以太坊网络,创建一个新的合约实例。
合约字节码是 Solidity 或其他智能合约语言编写的代码经过编译后生成的机器可执行格式,包含了合约的所有逻辑。
用户通过发起包含合约字节码的交易将合约代码部署到区块链上。合约部署后,EVM 会根据合约字节码生成一个新的合约地址,并分配相应的存储空间。
一旦合约被部署,它会获得一个唯一的合约地址。用户或其他合约可以通过该地址与合约交互。
2.合约调用是对已部署合约的函数进行调用,调用可以是外部调用或内部调用。外部调用是由用户或其他合约发起的。内部调用是由合约内部触发的其他函数调用,通常不会直接涉及到外部的账户。合约调用可能会改变合约的状态(例如修改状态变量)。
3.合约调用可能会更改合约的存储数据,存储操作消耗 Gas,并且在执行完毕后会永久存储在区块链上。
5. EVM 指令集(Opcodes)
算术运算指令
ADD:加法运算。将栈顶的两个值相加,结果推入栈中。
操作:PUSH A, B -> A + B
MUL:乘法运算。将栈顶的两个值相乘,结果推入栈中。
操作:PUSH A, B -> A * B
SUB:减法运算。将栈顶的两个值相减,结果推入栈中。
操作:PUSH A, B -> A - B
DIV:除法运算。将栈顶的两个值相除,结果推入栈中。
操作:PUSH A, B -> A / B
MOD:取余运算。将栈顶的两个值相除,结果为余数,推入栈中。
操作:PUSH A, B -> A % B
逻辑运算指令
AND:按位与运算。对栈顶的两个值执行按位与(AND)操作,结果推入栈中。
操作:PUSH A, B -> A & B
OR:按位或运算。对栈顶的两个值执行按位或(OR)操作,结果推入栈中。
操作:PUSH A, B -> A | B
XOR:按位异或运算。对栈顶的两个值执行按位异或(XOR)操作,结果推入栈中。
操作:PUSH A, B -> A ^ B
NOT:按位非运算。对栈顶的值执行按位非(NOT)操作,结果推入栈中。
操作:PUSH A -> ~A
SLOAD:从合约存储中加载数据。该指令从合约的存储中加载一个值并将其推入栈中。
操作:PUSH A -> SLOAD A
SSTORE:将数据存储到合约存储中。该指令将栈顶值存储到指定的合约存储位置。
操作:PUSH A, B -> SSTORE A = B
MLOAD:从内存加载数据。该指令从 EVM 内存中加载数据并将其推入栈中。
操作:PUSH A -> MLOAD A
MSTORE:将数据存储到内存中。该指令将栈顶的值存储到指定的内存位置。
操作:PUSH A, B -> MSTORE A = B
JUMP:跳转到指定的指令位置。该指令会将程序执行的控制流跳转到栈顶指定的地址。
操作:PUSH A -> JUMP A(跳转到地址 A)
JUMPI:条件跳转。若栈顶值为真,则跳转到指定的地址;否则继续执行。
操作:PUSH A, B -> JUMPI A(如果 B 为真,则跳转到地址 A)
PC:获取当前指令地址。该指令会将当前程序计数器(PC)推入栈中,用于获取执行位置。
操作:PC -> PUSH A
STOP:停止执行。该指令会立即停止当前的智能合约执行。
操作:STOP(停止合约执行)
RETURN:返回数据并停止执行。该指令用于从合约返回一段数据,之后停止合约执行。
操作:PUSH A, B -> RETURN(从 A 地址返回 B 长度的数据)
CALL:合约调用。该指令用于调用其他合约的函数,并可以传递数据和一定数量的 Gas。
操作:PUSH A, B, C, D -> CALL A, B, C, D(调用合约 A 的地址,传递 B 的 Gas,C 的数据和 D 的参数)
DELEGATECALL:委托调用。与 CALL 类似,但是在 DELEGATECALL 中,目标合约的存储是由调用合约控制的。它允许调用合约的上下文(存储、msg.sender 等)保持不变,而只是执行目标合约的代码。
操作:PUSH A, B, C, D -> DELEGATECALL A, B, C, D
STATICCALL:静态调用。与 CALL 相似,但是不允许修改合约状态(不允许发送 Ether 和修改存储)。这种调用通常用于读取合约状态。
操作:PUSH A, B, C, D -> STATICCALL A, B, C, D(执行目标合约的函数,只能读取数据)
6. Gas 计算
Gas 是 EVM 计算成本的衡量单位,每个 EVM 操作(Opcode)都会消耗一定数量的 Gas。
Gas 机制的主要目的是:
1.防止恶意代码无限执行(如死循环)
2.保障区块链资源的公平分配
3.为矿工提供计算资源的激励
交易执行时,每个指令的执行都需要消耗 Gas。用户在发起交易时需要指定 gas limit(最大 Gas 消耗)和 gas price(Gas 单价)。如果交易执行过程中 Gas 消耗超过 gas limit,交易会被回滚,但已消耗的 Gas 不会退还。