solidity 指令修改

admin
admin 2022年01月16日
  • 在其它设备中阅读本文章

solidity 编译后的字节码运行在以太坊虚拟机 EVM 中,指令修改需要同时修改编译器和虚拟机

关键文件

  1. Solidity 编译器项目地址:https://github.com/ethereum/solidity.git
  2. Token 的定义:solidity/liblangutil/Token.h;词法定义,比如 token{Add,+},将 + 与 Add 对应起来
  3. Instruction 的定义:solidity/libevmasm/Instruction.h;虚拟机执行的指令, 需要在 EVM 里实现
  4. Type 的定义:solidity/libsolidity/ast/Types.h;数据类型定义,包括魔术变量(block,msg...)的定义
  5. TypeProvider 的定义:solidity/libsolidity/ast/TypeProvider.h;所有数据类型声明
  6. Semantic 的定义:solidity/libevmasm/SemanticInformation.h; 语义信息定义,主要是指令对函数 pure,view,payable 属性的影响
  7. ExpressionCompiler 的定义:solidity/libsolidity/codegen/ExpressionCompiler.cpp;将 AST 语法树翻译成指令
  8. 以太坊区块链项目地址:https://github.com/ethereum/go-ethereum.git
  9. EVM 字节码定义:go-ethereum/core/vm/opcodes.go
  10. EVM 字节码属性定义:go-ethereum/core/vm/jump_table.go,将字节码和具体的操作绑定起来
  11. EVM 指令操作定义:go-ethereum/core/vm/instructions.go

添加 RANDOM 指令

编译器添加和 block.number 一样的 block.random 语法,虚拟机支持和 NUMBER 一样操作的 RANDOM 指令

编译器修改

1. 指令定义,代码位置 libevmasm/Instruction.h。在 enum calss Instruction 中找到 block 的相关属性,并在其后追加 RANDOM 指令,注意添加的指令不能与其他的冲突。

/// Virtual machine bytecode instruction.
enum class Instruction: uint8_t
{
    ......
    BLOCKHASH = 0x40,    ///< get hash of most recent complete block
    COINBASE,            ///< get the block's coinbase address
    TIMESTAMP,            ///< get the block's timestamp
    NUMBER,                ///< get the block's number
    DIFFICULTY,            ///< get the block's difficulty
    GASLIMIT,            ///< get the block's gas limit
    CHAINID,            ///< get the config's chainid param
    SELFBALANCE,        ///< get balance of the current account
    BASEFEE,            ///< get the block's basefee
    RANDOM,
    ......
}

上述定义为 16 进制,同时也需要有一个字符串的 "RANDOM" 与指令对应,代码位置 libevmasm/Instruction.cpp 中。

std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{
    ......
    { "DIFFICULTY", Instruction::DIFFICULTY },
    { "GASLIMIT", Instruction::GASLIMIT },
    { "CHAINID", Instruction::CHAINID },
    { "SELFBALANCE", Instruction::SELFBALANCE },
    { "BASEFEE", Instruction::BASEFEE },
    { "RANDOM", Instruction::RANDOM },
    ......
}
static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{
    ......
    { Instruction::DIFFICULTY,    { "DIFFICULTY",        0, 0, 1, false, Tier::Base } },
    { Instruction::GASLIMIT,    { "GASLIMIT",        0, 0, 1, false, Tier::Base } },
    { Instruction::CHAINID,        { "CHAINID",        0, 0, 1, false, Tier::Base } },
    { Instruction::SELFBALANCE,    { "SELFBALANCE",    0, 0, 1, false, Tier::Low } },
    { Instruction::BASEFEE,     { "BASEFEE",        0, 0, 1, false, Tier::Base } },
    { Instruction::RANDOM,        { "RANDOM",        0, 0, 1, false, Tier::Base } },
    ......
}

后面的 0,0,1,false,Tier::Base 是可变的,根据指令的需要。第一个默认为 0 即可,第二个 0 表示参数个数,第三个 1 表示需要 1 个返回值。false 可理解为只在虚拟机内部使用,如果涉及到数据的读写,这里要填成 true。最后的 Tier::Base 是 gasprice 的级别,根据需要填写即可。

2. 指令的处理,代码位置 libsolidity/codegen/ExpressionCompiler.cpp

bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
{
    ......
    case Type::Category::Magic:
        // we can ignore the kind of magic and only look at the name of the member
        if (member == "coinbase")
            m_context << Instruction::COINBASE;
        else if (member == "timestamp")
            m_context << Instruction::TIMESTAMP;
        else if (member == "difficulty")
            m_context << Instruction::DIFFICULTY;
        else if (member == "number")
            m_context << Instruction::NUMBER;
        else if (member == "random")
            m_context << Instruction::RANDOM;
    ......
}

3. 对函数、存储的影响。确定数据类型,代码位置 libsolidity/ast/Types.cpp

MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
{ //指定存储的数据类型
    ......
    case Kind::Block:
        return MemberList::MemberMap({
            {"coinbase", TypeProvider::payableAddress()},
            {"timestamp", TypeProvider::uint256()},
            {"blockhash", TypeProvider::function(strings{"uint"}, strings{"bytes32"}, FunctionType::Kind::BlockHash, false, StateMutability::View)},
            {"difficulty", TypeProvider::uint256()},
            {"number", TypeProvider::uint256()},
            {"random", TypeProvider::uint256()},
            {"gaslimit", TypeProvider::uint256()},
            {"chainid", TypeProvider::uint256()},
            {"basefee", TypeProvider::uint256()}
        });
    ......
}

对函数的影响:代码位置 libevmasm/Semanticlnformation.cpp

bool SemanticInformation::invalidInPureFunctions(Instruction _instruction)
{
    switch (_instruction)
    {
    ......
    case Instruction::TIMESTAMP:
    case Instruction::NUMBER:
    case Instruction::RANDOM: //增加的random指令影响函数的Pure属性,表示该函数不能使用pure关键字。
    case Instruction::DIFFICULTY:
    case Instruction::GASLIMIT:
    case Instruction::STATICCALL:
    case Instruction::SLOAD:
        return true;
    default:
        break;
    }
    return invalidInViewFunctions(_instruction);
}

修改虚拟机代码
1.random 指令的定义,代码位置 hvm/evm/opcodes.go

const (
    // 0x40 range - block operations
    BLOCKHASH OpCode = 0x40 + iota
    COINBASE
    TIMESTAMP
    NUMBER
    DIFFICULTY
    GASLIMIT
    RANDOM //新增
)
var opCodeToString = map[OpCode]string{
    ......
    NUMBER:     "NUMBER",
    DIFFICULTY: "DIFFICULTY",
    GASLIMIT:   "GASLIMIT",
    RANDOM:     "RANDOM", //新增
    ......
}
var stringToOp = map[string]OpCode{
    ......
    "NUMBER":         NUMBER,
    "DIFFICULTY":     DIFFICULTY,
    "GASLIMIT":       GASLIMIT,
    "RANDOM":         RANDOM, //新增
    ......
}

2. 指令操作的定义,代码位置 hvm/evm/jump_table.go , 添加指令的操作属性

    instructionSet[RANDOM] = operation{
        execute:       opRandom,
        gasCost:       constGasFunc(GasQuickStep),
        validateStack: makeStackFunc(0, 1),
        valid:         true,
    }

3. 上述操作码对应函数 opRandom 的定义,代码位置 hvm/evm/instrucitons.go,可参考 number 函数的定义

func opNumber(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
    stack.push(math.U256(new(big.Int).Set(evm.BlockNumber)))
    return nil, nil
}
func opRandom(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
    stack.push(math.U256(new(big.Int).Set(evm.BlockNumber)))
    return nil, nil
}

注:编译器和虚拟机里的字节码定义要一致,这样编译出来的程序才能正常运行