EVM 算术指令集
EVM(以太坊虚拟机)的算术指令集用于在堆栈上执行基本的算术操作; EVM 算术指令包含,ADD,MUL, SUB, DIV, SDIV, MOD, SMOD, ADDMOD, MULMOD, EXP, SIGNEXTEND。下面我将分别介绍 EVM 的各个算术指令集的功能作用。
1.ADD 指令详解
在以太坊虚拟机(EVM)中,ADD 指令用于对堆栈顶端的两个数值进行加法运算,并将结果推送到堆栈顶端。
1.1.操作码和气体费用
- 名称: ADD
- 操作码: 0x01
- 气体费用: 3 gas
1.2.功能
ADD 指令将堆栈顶端的两个数值相加,并将结果推送到堆栈顶端。
1.3.操作步骤
- 从堆栈中弹出两个值: 从堆栈中弹出两个数值。第一个弹出的数值称为 value1,第二个弹出的数值称为 value2。
- 将两个值相加: 计算 value1 + value2。
- 将结果推送到堆栈顶端: 将计算结果推送到堆栈顶端。
1.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x02
执行 ADD 指令后,堆栈状态将变为:
堆栈顶端 -> 0x05
1.4.1.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x03
0x02
执行 ADD 指令:
- 弹出 0x03(value1)。
- 弹出 0x02(value2)。
- 计算 0x03 + 0x02 = 0x05。
- 将 0x05 推送到堆栈顶端。
最终堆栈状态:
堆栈顶端 -> 0x05
1.5.注意事项
- ADD 指令用于无符号整数的加法运算,结果也为无符号整数。
- 当两个数值相加导致溢出时,结果将被截断为 256 位。EVM 不会抛出溢出错误,而是自动处理溢出情况。
2.MUL 指令详解
在以太坊虚拟机(EVM)中,MUL 指令用于对堆栈顶端的两个数值进行乘法运算,并将结果推送到堆栈顶端。
2.1.操作码和气体费用
- 名称: MUL
- 操作码: 0x02
- 气体费用: 5 gas
2.2.功能
MUL 指令将堆栈顶端的两个数值相乘,并将结果推送到堆栈顶端。
2.3.操作步骤
- 从堆栈中弹出两个值: 从堆栈中弹出两个数值。第一个弹出的数值称为 value1,第二个弹出的数值称为 value2。
- 将两个值相乘: 计算 value1 * value2。
- 将结果推送到堆栈顶端: 将计算结果推送到堆栈顶端。
2.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x02
执行 MUL 指令后,堆栈状态将变为:
堆栈顶端 -> 0x06
2.4.1.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x03
0x02
执行 MUL 指令:
- 弹出 0x03(value1)
- 弹出 0x02(value2)
- 计算 0x03 * 0x02 = 0x06
- 将 0x06 推送到堆栈顶端
最终堆栈状态:
堆栈顶端 -> 0x06
2.5.注意事项
- MUL 指令用于无符号整数的乘法运算,结果也为无符号整数。
- 当两个数值相乘导致溢出时,结果将被截断为 256 位。EVM 不会抛出溢出错误,而是自动处理溢出情况。
- MUL 指令执行时消耗的 gas 为 5 gas,因此在进行大规模计算时需要考虑 gas 消耗问题。
3.SUB 指令详解
在以太坊虚拟机(EVM)中,减法指令 SUB 用于执行堆栈顶端两个数值的减法操作,并将结果推送回堆栈顶端。
3.1.操作码和气体费用
- 操作码: 0x03
- 气体费用: 3 gas
3.2.功能
SUB 指令执行减法操作,从堆栈顶端的第二个数值中减去堆栈顶端的第一个数值,并将结果推送回堆栈顶端。
3.3.操作步骤
- 减法操作: 从堆栈顶端的第二个数值中减去堆栈顶端的第一个数值。
- 推送结果: 将减法操作的结果推送到堆栈顶端。
3.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x02
执行 SUB 指令后的堆栈状态将变为:
堆栈顶端 -> 0x01
3.4.1.详细操作步骤
初始堆栈状态:
堆栈顶端 -> 0x03
0x02
执行 SUB 指令:
- 从堆栈顶端的第二个数值 0x02 中减去堆栈顶端的第一个数值 0x03。
- 结果为 0x01。
最终堆栈状态:
堆栈顶端 -> 0x01
3.5. Solidity 代码样例
如果你想直接看到 EVM 字节码中 SUB 操作的代码示例,可以通过 Solidity 的 assembly 块来手动编写和生成相应的字节码。下面是一个简单的 Solidity 合约示例,展示如何使用 assembly 块手动执行减法操作并生成对应的 EVM 字节码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SubtractionAssembly {
function subtract(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result;
assembly {
// 将参数 a 和 b 压入堆栈
push {a}
push {b}
// 执行 SUB 操作
sub
// 将结果保存到 result 变量中
pop {result}
}
return result;
}
}
- subtract 函数: 接受两个参数 a 和 b,分别是要执行减法操作的数值。
- assembly 块: 使用 Solidity 的 assembly 块手动编写 EVM 字节码。 push {a} 和 push {b}: 将参数 a 和 b 推入堆栈顶部。 sub: 执行减法操作。 pop {result}: 将减法操作的结果从堆栈弹出,并将其存储在 Solidity 变量 result 中。
- 返回值: 将减法操作的结果作为函数的返回值返回。
3.6.注意事项
- SUB 指令执行的是有符号整数的减法操作。
- 在实际使用中,需要注意数值的溢出情况,确保合约逻辑的正确性和安全性。
- 每次执行 SUB 指令会消耗 3 gas,是一种低成本的堆栈操作。
4. DIV 指令详解
在以太坊虚拟机(EVM)的操作码中,DIV 指令用于执行两个无符号整数的除法操作。
4.1.DIV 指令概述
- 操作码: 0x04
- 操作码范围: 0x04 - 0x05
- 气体费用: 5 gas
4.2. 功能
DIV 指令执行整数除法操作,从堆栈顶端的第二个数值中除以堆栈顶端的第一个数值,并将结果推送回堆栈顶端。该操作是有符号的整数除法。
4.3.操作步骤
- 除法操作: 从堆栈顶端的第二个数值中除以堆栈顶端的第一个数值。
- 推送结果: 将除法操作的结果推送回堆栈顶端。
4.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x06
0x02
执行 DIV 指令后的堆栈状态将变为:
堆栈顶端 -> 0x03
这是因为 0x06 / 0x02 的结果为 0x03
4.5. Solidity 代码样例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract DivisionAssembly {
function divide(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result;
assembly {
// 将参数 a 和 b 压入堆栈
push {b}
push {a}
// 执行 DIV 操作
div
// 将结果保存到 result 变量中
pop {result}
}
return result;
}
}
divide 函数: 接受两个参数 a 和 b,分别是要执行除法操作的数值。
assembly 块: 使用 Solidity 的 assembly 块手动编写 EVM 字节码。
- push {b} 和 push {a}: 将参数 b 和 a 推入堆栈顶部。注意,Solidity 中的 assembly 块使用的是逆序排列参数。
- div: 执行整数除法操作。
- pop {result}: 将除法操作的结果从堆栈弹出,并将其存储在 Solidity 变量 result 中。
返回值: 将整数除法操作的结果作为函数的返回值返回。
4.6. 注意事项
- DIV 指令执行的是有符号整数的除法操作。
- 需要注意除数不能为零,否则会导致 EVM 抛出异常。
- 在 Solidity 中,整数除法运算同样使用 / 操作符,而不是直接调用 DIV 指令。
- 每次执行 DIV 指令会消耗 5 gas,是一种较为昂贵的堆栈操作。
5. MOD 指令详解
在以太坊虚拟机(EVM)中,MOD 指令用于执行取模操作(也称为求余操作)。这个指令将堆栈顶端的两个数值进行取模运算,并将结果推送回堆栈顶端。
5.1.指令概述
- 操作码: 0x06
- 气体费用: 5 gas
5.2.功能
MOD 指令从堆栈顶端的第二个数值中取堆栈顶端的第一个数值的模,并将结果推送回堆栈顶端。也就是说,它计算 a % b 的结果,其中 a 是堆栈顶端的第二个数值,b 是堆栈顶端的第一个数值。
5.3.操作步骤
- 取模操作: 从堆栈顶端的第二个数值中取堆栈顶端的第一个数值的模。
- 推送结果: 将取模操作的结果推送回堆栈顶端。
5.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03
0x0A
执行 MOD 指令后的堆栈状态将变为:
堆栈顶端 -> 0x01
这是因为 0x0A % 0x03 的结果为 0x01。
5.5.注意事项
- MOD 指令执行的是无符号整数的取模操作。
- 需要注意取模的第二个数值(即除数)不能为零,否则会导致 EVM 抛出异常。
- 在 Solidity 中,整数取模运算使用 % 操作符,而不是直接调用 MOD 指令。
- 每次执行 MOD 指令会消耗 5 gas,是一种较为昂贵的堆栈操作。
5.6.示例 Solidity 代码
在 Solidity 中,可以通过 assembly 块来使用 MOD 指令。以下是一个简单示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ModulusExample {
function modulus(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result;
assembly {
result := mod(a, b)
}
return result;
}
}
- modulus 函数: 接受两个参数 a 和 b,分别是要执行取模操作的数值。
- assembly 块: 使用 Solidity 的 assembly 块手动编写 EVM 字节码。 mod(a, b): 执行取模操作,并将结果存储在 Solidity 变量 result 中。
- 返回值: 将取模操作的结果作为函数的返回值返回。
6.EXP 指令详解
6.1.指令概述
- 操作码: 0x0A
- 气体费用: 基本费用 10 gas + 50 gas per byte of the exponent
6.2.功能
EXP 指令从堆栈顶端的第二个数值作为底数,堆栈顶端的第一个数值作为指数进行运算,并将结果推送回堆栈顶端。即它计算 base^exponent。
6.3.操作步骤
- 指数运算: 计算 base^exponent,其中 base 是堆栈顶端的第二个数值,exponent 是堆栈顶端的第一个数值。
- 推送结果: 将指数运算的结果推送回堆栈顶端。
6.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x03 // exponent
0x02 // base
执行 EXP 指令后的堆栈状态将变为:
堆栈顶端 -> 0x08 // 2^3 = 8
6.5.注意事项
- EXP 指令的气体费用包含基本费用和每字节指数费用。指数的每个字节消耗 50 gas。
- 在指数值较大时,运算的气体费用会显著增加。
- 需要注意指数运算的结果可能会非常大,需要考虑结果是否会超出 256 位。
6.6. Solidity 代码
在 Solidity 中,可以通过 assembly 块来使用 EXP 指令。以下是一个简单示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ExponentiationExample {
function exponentiate(uint256 base, uint256 exponent) public pure returns (uint256) {
uint256 result;
assembly {
result := exp(base, exponent)
}
return result;
}
}
- exponentiate 函数: 接受两个参数 base 和 exponent,分别是要执行指数运算的底数和指数。
- assembly 块: 使用 Solidity 的 assembly 块手动编写 EVM 字节码。 exp(base, exponent): 执行指数运算,并将结果存储在 Solidity 变量 result 中。
- 返回值: 将指数运算的结果作为函数的返回值返回。
7.SIGNEXTEND 指令详解
SIGNEXTEND 指令在以太坊虚拟机(EVM)中用于将一个数值扩展为带符号的数值。这个指令在处理不同大小的整数转换时特别有用。
7.1.指令概述
- 操作码: 0x0B
- 气体费用: 5 gas
7.2.功能
SIGNEXTEND 指令从堆栈中取出两个数值 k 和 x,然后对数值 x 进行带符号扩展。具体来说,它将 x 的第 k 字节的符号位扩展到整个数值的高位。
7.3.操作步骤
- 取出操作数: 从堆栈中取出两个数值,堆栈顶端的第一个数值是 k,第二个数值是 x。
- 带符号扩展: 对 x 进行带符号扩展,即将 x 的第 k 字节的符号位扩展到整个数值的高位。
- 推送结果: 将扩展后的数值推送回堆栈顶端。
7.4.示例
假设当前堆栈状态如下:
堆栈顶端 -> 0x01 // k
0x0000000000000000000000000000000000000000000000000000000000000080 // x
执行 SIGNEXTEND 指令后的堆栈状态将变为:
堆栈顶端 -> 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF // -128 带符号扩展后的结果
这是因为 x 的第 k 字节(即第 1 字节)的符号位是 1,所以扩展后,所有高位都填充符号位 1。
7.5.注意事项
- k 的范围为 0 到 31,表示字节位置。
- 如果 k 超出范围,则 SIGNEXTEND 指令不会改变 x 的值。
- 带符号扩展用于将较小的有符号数转换为较大的有符号数,同时保持其值。
7.6. Solidity 代码
在 Solidity 中,可以通过 assembly 块来使用 SIGNEXTEND 指令。以下是一个简单示例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SignExtendExample {
function signExtend(uint256 k, uint256 x) public pure returns (uint256) {
uint256 result;
assembly {
result := signextend(k, x)
}
return result;
}
}
- signExtend 函数: 接受两个参数 k 和 x,分别是扩展操作的字节位置和要扩展的数值。
- assembly 块: 使用 Solidity 的 assembly 块手动编写 EVM 字节码。 signextend(k, x): 执行带符号扩展操作,并将结果存储在 Solidity 变量 result 中。
- 返回值: 将带符号扩展操作的结果作为函数的返回值返回。
通过理解 SIGNEXTEND 指令的功能和使用场景,开发者可以更好地处理不同大小的整数转换,确保智能合约的正确性和高效运行。
8.其他指令集
8.1.SDIV
- Opcode: 0x05
- 描述: 两个有符号整数相除,返回商。
- 栈操作: POP a, b -> PUSH (a / b)
8.2.SMOD
Opcode: 0x07
描述: 两个有符号整数相除,返回余数。
栈操作: POP a, b -> PUSH (a % b)
8.3.ADDMOD
- Opcode: 0x08
- 描述: (a + b) % c
- 栈操作: POP a, b, c -> PUSH ((a + b) % c)
8.4.MULMOD
- Opcode: 0x09
- 描述: (a * b) % c
- 栈操作: POP a, b, c -> PUSH ((a * b) % c)