solidity 指令修改
solidity 编译后的字节码运行在以太坊虚拟机 EVM 中,指令修改需要同时修改编译器和虚拟机
关键文件
- Solidity 编译器项目地址:https://github.com/ethereum/solidity.git
- Token 的定义:solidity/liblangutil/Token.h;词法定义,比如 token{Add,+},将 + 与 Add 对应起来
- Instruction 的定义:solidity/libevmasm/Instruction.h;虚拟机执行的指令, 需要在 EVM 里实现
- Type 的定义:solidity/libsolidity/ast/Types.h;数据类型定义,包括魔术变量(block,msg...)的定义
- TypeProvider 的定义:solidity/libsolidity/ast/TypeProvider.h;所有数据类型声明
- Semantic 的定义:solidity/libevmasm/SemanticInformation.h; 语义信息定义,主要是指令对函数 pure,view,payable 属性的影响
- ExpressionCompiler 的定义:solidity/libsolidity/codegen/ExpressionCompiler.cpp;将 AST 语法树翻译成指令
- 以太坊区块链项目地址:https://github.com/ethereum/go-ethereum.git
- EVM 字节码定义:go-ethereum/core/vm/opcodes.go
- EVM 字节码属性定义:go-ethereum/core/vm/jump_table.go,将字节码和具体的操作绑定起来
- 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
}
注:编译器和虚拟机里的字节码定义要一致,这样编译出来的程序才能正常运行