EVM 控制流程,区块,Hash, 账户,交易, Log 与 Gas 相关的指令集

1.控制流程指令集

在以太坊虚拟机(EVM)中,控制流程指令用于管理程序的执行流程,包括函数调用、返回、条件分支等操作。主要的控制流程指令包括 JUMP、JUMPI、PC、JUMPDEST、CALL、CALLCODE、DELEGATECALL、STATICCALL、RETURN、REVERT、SELFDESTRUCT 等。以下是这些指令的详细介绍:

1.1.无条件跳转指令 JUMP

操作码: 0x56

功能: 无条件跳转到指定的程序计数器(PC)位置。

气体费用: 8 gas

操作步骤:

  • 从堆栈中弹出一个数值作为跳转目标。
  • 跳转到目标位置,该位置必须是 JUMPDEST。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract JUMPExample {
    function jump(uint256 destination) public pure returns (uint256) {
        assembly {
            jump(destination)
            destination := 42 // This line will not be reached
            jumpdest
        }
        return destination;
    }
}

1.2.条件跳转指令 JUMPI

操作码: 0x57

功能: 根据条件跳转到指定的程序计数器(PC)位置。

气体费用: 10 gas

操作步骤:

  • 从堆栈中弹出一个数值作为跳转目标。
  • 从堆栈中弹出一个条件数值。
  • 如果条件为非零,则跳转到目标位置,该位置必须是 JUMPDEST。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract JUMPIExample {
    function conditionalJump(uint256 destination, bool condition) public pure returns (uint256) {
        uint256 result = 0;
        assembly {
            let cond := condition
            jumpi(destination, cond)
            result := 42 // This line will be reached if condition is false
            jumpdest
            result := 1 // This line will be reached if condition is true
        }
        return result;
    }
}

1.3.程序计数器 PC

操作码: 0x58

功能: 返回当前的程序计数器(PC)位置,并将其推送到堆栈顶端。

气体费用: 2 gas

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PCExample {
    function getCurrentPC() public pure returns (uint256) {
        uint256 pc;
        assembly {
            pc := pc()
        }
        return pc;
    }
}

1.4.跳转目标 JUMPDEST

操作码: 0x5B

功能: 标记一个有效的跳转目标位置。

气体费用: 1 gas

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract JUMPDESTExample {
    function markJumpDest() public pure returns (uint256) {
        uint256 result = 0;
        assembly {
            jumpdest
            result := 42
        }
        return result;
    }
}

1.5.调用指令 CALL

操作码: 0xF1

功能: 调用另一个合约的方法,并传递以太币。

气体费用: 700 gas 基础费用 + 调用费用

操作步骤:

  • 从堆栈中弹出 7 个数值:gas、地址、value、input data offset、input data size、output data offset、output data size。
  • 调用目标合约的方法。
  • 将调用结果(成功或失败)推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CALLExample {
    function callAnotherContract(address target, bytes memory data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.call(data);
        require(success, "Call failed");
        return result;
    }
}

1.6.静态调用指令 STATICCALL

操作码: 0xFA

功能: 调用另一个合约的方法,不允许修改状态。

气体费用: 700 gas 基础费用 + 调用费用

操作步骤:

  • 从堆栈中弹出 6 个数值:gas、地址、input data offset、input data size、output data offset、output data size。
  • 调用目标合约的方法(静态调用)。
  • 将调用结果(成功或失败)推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract STATICCALLExample {
    function staticCallAnotherContract(address target, bytes memory data) public view returns (bytes memory) {
        (bool success, bytes memory result) = target.staticcall(data);
        require(success, "Static call failed");
        return result;
    }
}

1.7.返回指令 RETURN

操作码: 0xF3

功能: 结束当前执行,并返回指定的输出数据。

气体费用: 0 gas

操作步骤:

  • 从堆栈中弹出 2 个数值:output data offset、output data size。
  • 返回指定的输出数据。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract RETURNExample {
    function returnData() public pure returns (bytes memory) {
        bytes memory data = new bytes(32);
        assembly {
            mstore(add(data, 32), 42)
            return(add(data, 32), 32)
        }
    }
}

1.8.回退指令 REVERT

操作码: 0xFD

功能: 终止执行,并回退所有状态更改。

气体费用: 0 gas

操作步骤:

  • 从堆栈中弹出 2 个数值:revert data offset、revert data size。
  • 回退状态更改,并返回指定的回退数据。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract REVERTExample {
    function revertWithData() public pure {
        bytes memory data = new bytes(32);
        assembly {
            mstore(add(data, 32), 42)
            revert(add(data, 32), 32)
        }
    }
}

1.9.自毁指令 SELFDESTRUCT

操作码: 0xFF

功能: 销毁当前合约,并将其剩余的以太币发送到指定地址。

气体费用: 0 gas

操作步骤:

  • 从堆栈中弹出一个数值作为受益地址。
  • 销毁当前合约,并将剩余的以太币发送到受益地址。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SELFDESTRUCTExample {
    function destroy(address payable beneficiary) public {
        selfdestruct(beneficiary);
    }
}

2. 区块相关的指令集

在以太坊虚拟机(EVM)中,区块相关的指令用于获取区块链状态信息,这些信息在智能合约的执行过程中可能是必需的。主要的区块相关指令包括 BLOCKHASH、COINBASE、TIMESTAMP、NUMBER、DIFFICULTY、GASLIMIT、CHAINID 和 BASEFEE。以下是这些指令的详细介绍:

2.1.区块哈希指令 BLOCKHASH

操作码: 0x40

功能: 获取指定区块号的区块哈希。

气体费用: 20 gas

操作步骤:

  • 从堆栈中弹出一个数值作为区块号。
  • 推送该区块号的区块哈希到堆栈顶端(只能获取最近 256 个区块的哈希)。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BlockHashExample {
    function getBlockHash(uint256 blockNumber) public view returns (bytes32) {
        bytes32 blockHash;
        assembly {
            blockHash := blockhash(blockNumber)
        }
        return blockHash;
    }
}

2.2.挖矿奖励地址指令 COINBASE

操作码: 0x41

功能: 获取当前区块的矿工地址(coinbase)。

气体费用: 2 gas

操作步骤:

  • 将当前区块的矿工地址推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CoinbaseExample {
    function getCoinbase() public view returns (address) {
        address coinbase;
        assembly {
            coinbase := coinbase()
        }
        return coinbase;
    }
}

2.3.区块时间戳指令 TIMESTAMP

操作码: 0x42

功能: 获取当前区块的时间戳。

气体费用: 2 gas

操作步骤:

  • 将当前区块的时间戳推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract TimestampExample {
    function getTimestamp() public view returns (uint256) {
        uint256 timestamp;
        assembly {
            timestamp := timestamp()
        }
        return timestamp;
    }
}

2.4.区块号指令 NUMBER

操作码: 0x43

功能: 获取当前区块的区块号。

气体费用: 2 gas

操作步骤:

  • 将当前区块的区块号推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BlockNumberExample {
    function getBlockNumber() public view returns (uint256) {
        uint256 blockNumber;
        assembly {
            blockNumber := number()
        }
        return blockNumber;
    }
}

2.5.区块难度指令 DIFFICULTY

操作码: 0x44

功能: 获取当前区块的难度。

气体费用: 2 gas

操作步骤:

  • 将当前区块的难度推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DifficultyExample {
    function getDifficulty() public view returns (uint256) {
        uint256 difficulty;
        assembly {
            difficulty := difficulty()
        }
        return difficulty;
    }
}

2.6.区块气体限制指令 GASLIMIT

操作码: 0x45

功能: 获取当前区块的气体限制。

气体费用: 2 gas

操作步骤:

  • 将当前区块的气体限制推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasLimitExample {
    function getGasLimit() public view returns (uint256) {
        uint256 gasLimit;
        assembly {
            gasLimit := gaslimit()
        }
        return gasLimit;
    }
}

2.7.链ID指令 CHAINID

操作码: 0x46

功能: 获取当前链的链ID。

气体费用: 2 gas

操作步骤:

  • 将当前链的链ID推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ChainIDExample {
    function getChainID() public view returns (uint256) {
        uint256 chainID;
        assembly {
            chainID := chainid()
        }
        return chainID;
    }
}

2.8.基础费用指令 BASEFEE

操作码: 0x48

功能: 获取当前区块的基础费用。

气体费用: 2 gas

操作步骤:

  • 将当前区块的基础费用推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BaseFeeExample {
    function getBaseFee() public view returns (uint256) {
        uint256 baseFee;
        assembly {
            baseFee := basefee()
        }
        return baseFee;
    }
}

3. Hash 指令集

在以太坊虚拟机(EVM)中,Hash 指令主要用于计算各种哈希值。这些指令在智能合约中非常重要,因为它们可以用于数据的验证和保护。主要的 Hash 指令包括 SHA3。以下是这些指令的详细介绍:

3.1.SHA3 指令

操作码: 0x20

功能: 计算给定内存区域的 Keccak-256 哈希值。

气体费用: 30 gas 基础费用 + 每个字节 6 gas

操作步骤:

  • 从堆栈中弹出两个数值:内存区域的起始位置和内存区域的大小。
  • 计算指定内存区域的 Keccak-256 哈希值。
  • 将计算出的哈希值推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SHA3Example {
    function computeHash(bytes memory data) public pure returns (bytes32) {
        bytes32 hash;
        assembly {
            // Load the start position of the data in memory
            let dataPtr := add(data, 32)
            // Load the length of the data
            let dataLen := mload(data)
            // Compute the hash using SHA3
            hash := keccak256(dataPtr, dataLen)
        }
        return hash;
    }
}

示例解释

  • computeHash 函数: 接受一个 bytes 类型的参数 data,并计算该数据的 Keccak-256 哈希值。使用内联汇编 (assembly) 直接操作内存,首先加载数据的起始位置和长度,然后调用 keccak256 指令来计算哈希值。

3.2.Keccak-256 哈希

Keccak-256 是一种加密哈希函数,它是 SHA-3 的一种变体。EVM 使用 Keccak-256 而不是标准的 SHA-3。计算哈希时,EVM 需要消耗一定的 Gas 费用,费用与数据的大小成正比。

用途:

  • 数据验证: 用于验证数据的完整性。
  • 唯一标识: 用于生成数据的唯一标识符。
  • 密码学: 用于密码学操作和安全协议。

操作步骤示例:

  • 准备数据: 将需要哈希的数据加载到内存中。
  • 调用 SHA3: 使用 keccak256 指令计算哈希值。
  • 处理结果: 将计算出的哈希值用于进一步的操作,如验证或存储。

完整的 Solidity 合约示例

以下是一个完整的 Solidity 合约示例,演示了如何使用 SHA3 指令计算哈希值:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract HashExample {
    // 计算字符串的 Keccak-256 哈希值
    function hashString(string memory input) public pure returns (bytes32) {
        bytes32 result;
        assembly {
            // 加载字符串数据的起始位置
            let inputPtr := add(input, 32)
            // 加载字符串的长度
            let inputLen := mload(input)
            // 使用 keccak256 计算哈希
            result := keccak256(inputPtr, inputLen)
        }
        return result;
    }

    // 计算两个整数的 Keccak-256 哈希值
    function hashIntegers(uint256 a, uint256 b) public pure returns (bytes32) {
        bytes32 result;
        assembly {
            // 分配 64 字节的内存区域
            let memPtr := mload(0x40)
            // 将整数 a 和 b 分别存储在内存中
            mstore(memPtr, a)
            mstore(add(memPtr, 32), b)
            // 使用 keccak256 计算哈希
            result := keccak256(memPtr, 64)
        }
        return result;
    }
}

在这个示例中,我们定义了两个函数:

  • hashString: 接受一个字符串参数,并计算其 Keccak-256 哈希值。
  • hashIntegers: 接受两个整数参数,并计算它们组合后的 Keccak-256 哈希值。

通过这些函数,可以看到如何在 Solidity 合约中使用 SHA3 指令来计算哈希值,并理解其在智能合约中的应用。

4. 账户指令集

在以太坊虚拟机(EVM)中,账户指令用于与账户相关的信息和操作。这些指令主要涉及获取账户的余额、代码、代码大小、代码哈希、地址等。主要的账户指令包括 BALANCE、EXTCODESIZE、EXTCODECOPY、EXTCODEHASH 和 SELFDESTRUCT。以下是这些指令的详细介绍:

4.1.账户余额指令 BALANCE

操作码: 0x31

功能: 获取指定账户的余额。

气体费用: 700 gas

操作步骤:

  • 从堆栈中弹出一个数值作为账户地址。
  • 推送该账户的余额到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract BalanceExample {
    function getBalance(address account) public view returns (uint256) {
        uint256 balance;
        assembly {
            balance := balance(account)
        }
        return balance;
    }
}

4.2.外部账户代码大小指令 EXTCODESIZE

操作码: 0x3B

功能: 获取指定账户的代码大小。

气体费用: 700 gas

操作步骤:

  • 从堆栈中弹出一个数值作为账户地址。
  • 推送该账户的代码大小到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExtCodeSizeExample {
    function getCodeSize(address account) public view returns (uint256) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size;
    }
}

4.3.外部账户代码复制指令 EXTCODECOPY

操作码: 0x3C

功能: 复制指定账户的代码到内存中。

气体费用: 700 gas + 每字节 3 gas

操作步骤:

  • 从堆栈中弹出 4 个数值:账户地址、内存起始位置、代码起始位置、代码大小。
  • 将指定账户的代码复制到内存中。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExtCodeCopyExample {
    function getCode(address account) public view returns (bytes memory) {
        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        bytes memory code = new bytes(size);
        assembly {
            extcodecopy(account, add(code, 32), 0, size)
        }
        return code;
    }
}

4.4.外部账户代码哈希指令 EXTCODEHASH

操作码: 0x3F

功能: 获取指定账户代码的 Keccak-256 哈希。

气体费用: 400 gas

操作步骤:

  • 从堆栈中弹出一个数值作为账户地址。
  • 推送该账户代码的 Keccak-256 哈希到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExtCodeHashExample {
    function getCodeHash(address account) public view returns (bytes32) {
        bytes32 codehash;
        assembly {
            codehash := extcodehash(account)
        }
        return codehash;
    }
}

5. 交易指令集

在以太坊虚拟机(EVM)中,交易指令主要用于获取当前交易的信息以及执行发送以太币等操作。这些指令包括 CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2, CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY, CODESIZE, CODECOPY, 和 GASPRICE。以下是这些指令的详细介绍:

5.1.交易调用指令 CALL, CALLCODE, DELEGATECALL, STATICCALL

这些指令用于调用其他合约或执行代理调用。

5.1.1.CALL

操作码: 0xF1

功能: 调用另一个合约,支持发送以太币。

气体费用: 调用基本费用 + 所需 gas + 调用的子合约执行所需的 gas

操作步骤:

  • 从堆栈中弹出 7 个参数:gas、目标地址、以太币数额、输入数据起始位置、输入数据大小、输出数据起始位置、输出数据大小。
  • 调用指定的合约地址,传递指定的以太币和输入数据,获取输出数据。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallExample {
    function callAnotherContract(address target, uint256 value, bytes calldata data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.call{value: value}(data);
        require(success, "Call failed");
        return result;
    }
}

5.1.2.CALLCODE

操作码: 0xF2

功能: 调用另一个合约的代码,但保持当前合约的存储。

气体费用: 类似 CALL

操作步骤:

  • 从堆栈中弹出 7 个参数。
  • 调用指定的合约代码,但使用当前合约的存储。

5.1.3.DELEGATECALL

操作码: 0xF4

功能: 类似 CALLCODE,但继承调用者的 msg.sender 和 msg.value。

气体费用: 类似 CALL

操作步骤:

  • 从堆栈中弹出 6 个参数:gas、目标地址、输入数据起始位置、输入数据大小、输出数据起始位置、输出数据大小。
  • 调用指定的合约代码,继承调用者的 msg.sender 和 msg.value。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DelegateCallExample {
    function delegateCallAnotherContract(address target, bytes calldata data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.delegatecall(data);
        require(success, "Delegate call failed");
        return result;
    }
}

5.1.4.STATICCALL

操作码: 0xFA

功能: 以静态方式调用另一个合约,不允许状态更改。

气体费用: 类似 CALL

操作步骤:

  • 从堆栈中弹出 6 个参数。
  • 以静态方式调用指定的合约,不允许状态更改。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StaticCallExample {
    function staticCallAnotherContract(address target, bytes calldata data) public view returns (bytes memory) {
        (bool success, bytes memory result) = target.staticcall(data);
        require(success, "Static call failed");
        return result;
    }
}

5.2.合约创建指令 CREATE, CREATE2

5.2.1.CREATE

操作码: 0xF0

功能: 创建一个新的合约。

气体费用: 32000 gas + 合约创建所需的 gas

操作步骤:

  • 从堆栈中弹出 3 个参数:以太币数额、输入数据起始位置、输入数据大小。
  • 创建新合约,并传递初始化代码。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CreateExample {
    function createNewContract(bytes memory bytecode) public returns (address) {
        address newContract;
        assembly {
            newContract := create(0, add(bytecode, 32), mload(bytecode))
        }
        require(newContract != address(0), "Contract creation failed");
        return newContract;
    }
}

5.2.2.CREATE2

操作码: 0xF5

功能: 使用指定的 salt 创建一个新的合约,确保合约地址的可预测性。

气体费用: 类似 CREATE

操作步骤:

  • 从堆栈中弹出 4 个参数:以太币数额、输入数据起始位置、输入数据大小、salt。
  • 创建新合约,使用 salt 生成地址。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Create2Example {
    function createNewContractWithSalt(bytes memory bytecode, bytes32 salt) public returns (address) {
        address newContract;
        assembly {
            newContract := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        require(newContract != address(0), "Contract creation failed");
        return newContract;
    }
}

5.3.交易信息指令 CALLVALUE, CALLDATALOAD, CALLDATASIZE, CALLDATACOPY

3.1.CALLVALUE

操作码: 0x34

功能: 获取当前调用中发送的以太币数量。

气体费用: 2 gas

操作步骤:

  • 将当前调用中发送的以太币数量推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallValueExample {
    function getCallValue() public payable returns (uint256) {
        uint256 value;
        assembly {
            value := callvalue()
        }
        return value;
    }
}

5.3.2.CALLDATALOAD

操作码: 0x35

功能: 从调用数据中加载一个 32 字节的值。

气体费用: 3 gas

操作步骤:

  • 从堆栈中弹出一个参数:加载位置。
  • 将加载的 32 字节值推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallDataLoadExample {
    function loadCallData(uint256 position) public pure returns (bytes32) {
        bytes32 data;
        assembly {
            data := calldataload(position)
        }
        return data;
    }
}

5.3.3.CALLDATASIZE

操作码: 0x36

功能: 获取调用数据的大小。

气体费用: 2 gas

操作步骤:

  • 将调用数据的大小推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallDataSizeExample {
    function getCallDataSize() public pure returns (uint256) {
        uint256 size;
        assembly {
            size := calldatasize()
        }
        return size;
    }
}

5.3.4.CALLDATACOPY

操作码: 0x37

功能: 将调用数据复制到内存中。

气体费用: 3 gas + 每字节 3 gas

操作步骤:

  • 从堆栈中弹出 3 个参数:内存起始位置、数据起始位置、数据大小。
  • 将调用数据复制到内存中。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CallDataCopyExample {
    function copyCallData(uint256 start, uint256 length) public pure returns (bytes memory) {
        bytes memory data = new bytes(length);
        assembly {
            calldatacopy(add(data, 32), start, length)
        }
        return data;
    }
}

5.4.合约代码指令 CODESIZE, CODECOPY

5.4.1.CODESIZE

操作码: 0x38

功能: 获取当前合约的代码大小。

气体费用: 2 gas

操作步骤:

  • 将当前合约的代码大小推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CodeSizeExample {
    function getCodeSize() public view returns (uint256) {
        uint256 size;
        assembly {
            size := codesize()
        }
        return size;
    }
}

5.4.2.CODECOPY

操作码: 0x39

功能: 将当前合约的代码复制到内存中。

气体费用: 3 gas + 每字节 3 gas

操作步骤:

  • 从堆栈中弹出 3 个参数:内存起始位置、代码起始位置、代码大小。
  • 将当前合约的代码复制到内存中。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CodeCopyExample {
    function copyCode(uint256 start, uint256 length) public view returns (bytes memory) {
        bytes memory code = new bytes(length);
        assembly {
            codecopy(add(code, 32), start, length)
        }
        return code;
    }
}

5.5.获取当前 Gas 价格 GASPRICE

操作码: 0x3A

功能: 获取当前交易的 gas 价格。

气体费用: 2 gas

操作步骤:

  • 将当前交易的 gas 价格推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasPriceExample {
    function getGasPrice() public view returns (uint256) {
        uint256 gasPrice;
        assembly {
            gasPrice := gasprice()
        }
        return gasPrice;
    }
}

5.6.完整示例

以下是一个综合使用上述指令的完整示例合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract EVMSample {

    function callExample(address target, uint256 value, bytes calldata data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.call{value: value}(data);
        require(success, "Call failed");
        return result;
    }

    function delegateCallExample(address target, bytes calldata data) public returns (bytes memory) {
        (bool success, bytes memory result) = target.delegatecall(data);
        require(success, "Delegate call failed");
        return result;
    }

    function staticCallExample(address target, bytes calldata data) public view returns (bytes memory) {
        (bool success, bytes memory result) = target.staticcall(data);
        require(success, "Static call failed");
        return result;
    }

    function createExample(bytes memory bytecode) public returns (address) {
        address newContract;
        assembly {
            newContract := create(0, add(bytecode, 32), mload(bytecode))
        }
        require(newContract != address(0), "Contract creation failed");
        return newContract;
    }

    function create2Example(bytes memory bytecode, bytes32 salt) public returns (address) {
        address newContract;
        assembly {
            newContract := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        require(newContract != address(0), "Contract creation failed");
        return newContract;
    }

    function getCallValue() public payable returns (uint256) {
        uint256 value;
        assembly {
            value := callvalue()
        }
        return value;
    }

    function loadCallData(uint256 position) public pure returns (bytes32) {
        bytes32 data;
        assembly {
            data := calldataload(position)
        }
        return data;
    }

    function getCallDataSize() public pure returns (uint256) {
        uint256 size;
        assembly {
            size := calldatasize()
        }
        return size;
    }

    function copyCallData(uint256 start, uint256 length) public pure returns (bytes memory) {
        bytes memory data = new bytes(length);
        assembly {
            calldatacopy(add(data, 32), start, length)
        }
        return data;
    }

    function getCodeSize() public view returns (uint256) {
        uint256 size;
        assembly {
            size := codesize()
        }
        return size;
    }

    function copyCode(uint256 start, uint256 length) public view returns (bytes memory) {
        bytes memory code = new bytes(length);
        assembly {
            codecopy(add(code, 32), start, length)
        }
        return code;
    }

    function getGasPrice() public view returns (uint256) {
        uint256 gasPrice;
        assembly {
            gasPrice := gasprice()
        }
        return gasPrice;
    }
}

这个合约演示了如何使用各种 EVM 交易指令来进行合约调用、创建、数据加载和复制、获取交易信息等操作。每个函数对应一个具体的指令,展示了其实际应用和返回结果。

6. Log 指令集

在以太坊虚拟机(EVM)中,Log 指令用于向区块链中写入日志数据。日志数据可以在以太坊客户端(如以太坊节点或区块浏览器)中查看,并且在 Solidity 合约中使用事件(event)来定义和触发日志记录。以下是与 Log 相关的 EVM 指令及其详细介绍:

6.1.LOG0, LOG1, LOG2, LOG3, LOG4 指令

功能: 将日志数据写入区块链。

操作码:

  • LOG0: 0xA0
  • LOG1: 0xA1
  • LOG2: 0xA2
  • LOG3: 0xA3
  • LOG4: 0xA4

气体费用: 375 gas + 8 gas/byte

操作步骤:

  • LOG0 至 LOG4 指令从堆栈中弹出若干个参数,将日志数据写入区块链。
  • 每个 LOG 指令都需要以下参数: 日志主题(topic)数量 日志主题(topic)数组起始位置 日志数据起始位置 日志数据大小

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LogExample {
    event LogEvent(address indexed sender, uint256 indexed value);

    function logData(uint256 value) public {
        emit LogEvent(msg.sender, value);
    }
}
  • 在上面的示例中,LogExample 合约定义了一个事件 LogEvent,用于记录发送者地址和值。
  • emit LogEvent(msg.sender, value) 语句将触发日志记录。当函数 logData 被调用时,会在区块链上写入相应的日志数据。
  • Solidity 会自动为事件生成日志主题(topic),例如 msg.sender 和 value。

6.2.使用日志的优势

  • 数据记录: 日志可以用来记录合约执行过程中的重要事件和数据变化。
  • 可审计性: 日志是智能合约的一种审计和调试工具,可以帮助开发者和用户了解合约的行为和状态变化。
  • 事件通知: 客户端应用程序可以通过订阅合约事件来获取即时通知和更新。

7. Gas 指令集

在以太坊虚拟机(EVM)中,Gas 指令主要用于管理和获取当前交易的 gas 相关信息。以下是与 gas 相关的主要指令及其详细介绍:

7.1.GAS 指令

操作码: 0x5A

功能: 返回剩余的 gas。

气体费用: 2 gas

操作步骤:

  • 将当前执行上下文中剩余的 gas 推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasExample {
    function getRemainingGas() public view returns (uint256) {
        uint256 gasRemaining;
        assembly {
            gasRemaining := gas()
        }
        return gasRemaining;
    }
}

7.2.GASLIMIT 指令

操作码: 0x45

功能: 获取当前区块的 gas 限制。

气体费用: 2 gas

操作步骤:

  • 将当前区块的 gas 限制推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasLimitExample {
    function getGasLimit() public view returns (uint256) {
        uint256 gasLimit;
        assembly {
            gasLimit := gaslimit()
        }
        return gasLimit;
    }
}

7.3.GASPRICE 指令

操作码: 0x3A

功能: 获取当前交易的 gas 价格。

气体费用: 2 gas

操作步骤:

  • 将当前交易的 gas 价格推送到堆栈顶端。

示例:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasPriceExample {
    function getGasPrice() public view returns (uint256) {
        uint256 gasPrice;
        assembly {
            gasPrice := gasprice()
        }
        return gasPrice;
    }
}

7.4.GAS 指令的应用

在智能合约中,开发者可以使用 gas() 指令来获取当前剩余的 gas,并使用 gaslimit() 和 gasprice() 指令来获取当前区块的 gas 限制和交易的 gas 价格。这些信息对于优化合约性能和管理 gas 成本非常重要。

7.5.组合示例

以下是一个综合使用 gas 指令的合约示例,展示了如何在合约中获取并显示当前交易和区块的 gas 信息:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GasInfo {
    function getGasInfo() public view returns (uint256, uint256, uint256) {
        uint256 gasRemaining;
        uint256 gasLimit;
        uint256 gasPrice;

        assembly {
            gasRemaining := gas()
            gasLimit := gaslimit()
            gasPrice := gasprice()
        }

        return (gasRemaining, gasLimit, gasPrice);
    }
}
  • getGasInfo 函数同时返回当前剩余的 gas、当前区块的 gas 限制和当前交易的 gas 价格。
  • 使用 assembly 块来调用 EVM 的 gas、gaslimit 和 gasprice 指令。

全部评论(0)