EVM底层探索:字节码级分析最小化代理标准EIP1167

概述

前往我的博客获得更好地阅读体验。

本文主要介绍最小化代理合约EIP1167的相关内容。为了实现最小化,EIP1167使用了bytecode(字节码)作为主要编码方式,即直接使EVM汇编指令进行编写。本文将在openzeppelin提供的合约基础上,为读者逐个字节码的解析EIP1167,帮助读者理解EVM底层汇编和EIP1167的实现原理。

注意虽然EIP1167也实现了代理合约,但其不具有合约升级能力,如果你希望构造可升级合约,请阅读以下文章:

如果读者没有代理合约开发经验,也建议阅读上文获得一些关于代理合约的基本知识。

建议读者在阅读后文之前可以简单读一下EIP1167标准

openzeppelin实现

我们在此处首先给出openzeppelin的合约实现,代码如下:

function clone(address implementation) internal returns (address instance) {
    /// @solidity memory-safe-assembly
    assembly {
        let ptr := mload(0x40)
        mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
        mstore(add(ptr, 0x14), shl(0x60, implementation))
        mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
        instance := create(0, ptr, 0x37)
    }
    require(instance != address(0), "ERC1167: create failed");
}

上述代码描述了代理合约生成的基本结构。我们采用从顶向下分析的方法,首先关注合约生成的核心代码instance := create(0, ptr, 0x37)。查阅EVM汇编表格,我们可以知道此函数接受三个变量,分别是:

  • value, 传递给新合约的ETH(以wei计费)
  • offset, 新合约代码在内存中的起始位置
  • size, 新合约的代码长度

本质上来说,此函数实现获取内存中的合约代码并将其进行部署的功能。在此处,我们没有向新合约传递ETH,规定了新合约的代码在内存中的起始位置为ptr,长度为0x37 byte,即55 byte 或 110 个16进制数字。

我们可以断定以下代码的功能是构造新合约的字节码:

let ptr := mload(0x40)
mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(ptr, 0x14), shl(0x60, implementation))
mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)

正如前文所述,由于EIP1167完全使用字节码编程,而solidity对内存级控制并不擅长,所以我们在此处只能提供内联汇编实现代码构建。当然,对于一般的solidity合约,你可以参考此文

接下来,我们对字节码构造部分进行解释,注意在此节中,我们不会指明生成的字节码的作用,此部分内容位于下一节。

此处用到了implementation变量,即需要被代理合约的地址,我们在此处假设其值为0xbebebebebebebebebebebebebebebebebebebebe

let ptr := mload(0x40)。此处对ptr的值进行初始化。初始化的方法是读取(使用mload函数读取指定地址的值) 0x40 地址的值。此处使用0x40地址进行读取的原因是此地址内存储着空闲内存的起始位置。在此处举一个例子,如果你的合约已经把0x60前的内存都填满了,读取0x40位置时,会获得0x61这个值。使用0x40中存储的地址可以有效避免内存覆写冲突问题的出现。

实际上mload(0x40)返回的是内存目前的占用量,其等同于空闲内存的起始位置,具体可以参考Layout in Memory

当我们获取到空闲内存的起始位置后,我们接下来就可以构造EIP1167合约的字节码。

代码中各个汇编函数的作用如下:

  • mstore(offset, value)的作用为向指定内存地址内写入value数据。注意offset的单位为bytevalue的长度必须为32 byte
  • add(a, b)的作用为a + b
  • shl(shift, value)的作用为将value左移shiftbit。注意单位为bit

综合以上内容,我们可以得到每行代码的具体作用。

mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000),我们首先在ptr后插入了0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000(32 byte)。

mstore(add(ptr, 0x14), shl(0x60, implementation)),我们首先将implementation的地址(20 byte)通过shl左移0x60 bit,即12 byte,形成32 byte的标准数据。得到标准数据后,我们将数据写入ptr + 0x14处,即ptr20 byte(40个16进制数),最终形成以下数据:

0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe000000000000000000000000

最后,我们通过mstore(add(ptr, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)写入数据,形成以下数据:

0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000

根据上文给出的create的参数,我们发现部署合约时仅读取此字节码的前0x37 byte的数据,即使用以下数据构造合约:

0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3

关于此此字节码的作用,我们会在下文进行解释。

上述流程可以用下图进行概括:
EIP1167 Memory

此图展示了上述汇编代码对内存的修改情况。其中最上方的ptrptr + 0x14ptr + 0x28等值表示当前的内存地址,0x14等值的单位均为byte

字节码解析

运行流程

在进行字节码分析前,我们需要在顶层理解EIP1167是如何运行的,其核心在于delegatecall的使用。

合约运行分为以下几个步骤:

  1. 获得calldata,用户发送的calldata中包含需要调用的函数和对应的参数,我们需要获得calldata以便于后期进行转发。
  2. 使用delegatecall发送calldata。合约在获得calldata后可以通过delegatecall进行委托调用,代理合约会把被代理合约内的代码拉取到本地输入calldata进行运行,并将结果保存到代理合约内。
  3. 获得delegatecall返回的结果并并储存到内存中
  4. 向用户返回结果或错误

以上就是EIP1167的运行流程,接下来我们会解释如何通过0x3d602d80600a3d3981f3363d3d373d3d3d363d73bebebebebebebebebebebebebebebebebebebebe5af43d82803e903d91602b57fd5bf3字节码实现这一功能。

初始化

我们将智能合约分为两部分,一部分是在创建合约时运行的代码,我们称为创建代码(creation code 或 Deploy code),另一部分则是逻辑代码(runtime code)。

前者主要实现以下功能:

  • 运行constructor构造器函数
  • 进行合约变量初始化
  • runtime code复制到内存中

一个比较好的类比是创建代码类似软件的安装包,它会根据用户的输入选择安装文件夹释放文件并进行软件的初始化。类比无法使我们接近本质,所以我们在此处给出go-ethereum的合约创建源代码:

func (evm *EVM) create
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WongSSH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值