您正在查看: Ethereum 分类下的文章

以太坊迈向full sharding的第一步,EIP-4844全面解析

简单概括,KZG Ceremony 是实现 EIP-4844 不可绕过的一个初始化环节,而 EIP-4844 是实现以太坊sharding过程中的先行版本。

一、Sharding: 以太坊扩容的长久之计

  • Sharding 从数据可用性角度扩容,而 rollup 主要从执行层面扩容,一起缓解主网拥堵问题;我认为sharding 可能是突破区块链不可能三角的一次尝试。
  • 下面这张以太坊区块大小的走势图可以从另一个角度说明数据层面扩容的必要性。从创世区块开始,以太坊从底层架构到上层应用都在不断地快速迭代,但平均区块大小仍为 90Kb 左右,最高点也没有质的突破。虽然Rollup从计算层切入,解决以太坊拥堵问题,但性能仍然受制于layer 1的数据存储能力。
  • 由于实现过程的复杂度和安全性考虑,以太坊开发团队将 sharding 分成了多个阶段,其中就包括最近提到的 proto-danksharding 和 danksharding 。整个过程将会是一个历时数年的更新;
  • 在目前的以太坊的数据存储模式下,只有少数特定高配置的机器可以加入网络成为节点,而 full sharding 之后 ethereum 不再需要每个节点都保存全部数据,在降低主网数据存储成本的同时加强了安全性(成为节点的门槛降低,去中心化程度进一步提升,同时降低被攻击的风险)。

二、EIP-4844: 短期高回报,Sharding的精简先行版

EIP-4844 = Proto-Danksharding;
Proto来自以太坊研究者的名称

由于实现 danksharding(下一节会分析)的复杂度很高,开发周期至少是以年为单位的。因此 proto-danksharding 是实现 danksharding 前对以太坊的扩容方案,主要实现了 danksharding 中的交易格式、precompile 等设计;

1. Proto-danksharding概述

Proto-Danksharding 主要引入了一种新的 transaction type,也就是 blob-carrying transaction。至此,Rollup 的数据通过blob 的形式以更低的成本向layer 1传输,并作非永久存储。同时,blob 远大于现在的 calldata,可以更好地支持 rollup 上的高TPS。

关于blob:

  • 每个transaction最多挂2个 blob;
  • 每个block理想状态包含8个 blob,约为1MB,最多包含16个 blob,约为2 MB;
  • Blob 不需要像calldata一样作为 history log 被永久存储;
  • 相比 danksharding,节点还是需要对完整的 DA进行验证。

2.Blob-carrying transaction解读

给transaction挂上blob(数据单元)

作用

参考Vitalik在提案中给出的图,Data blob 和当前的 calldata 相似,rollup 可以将交易、证明等数据通过blob上传到 layer 1 来保证 data availability。

成本

Data blob 的设计初衷是支持 rollup 高通量的交易,相比同等大小的 calldata(使用链上存储),blob 的成本将会降低很多(不需要永久存储)。因此,rollup 在维持数据可用性上花费的gas相比之前会显著降低。

容量

每个blob的大小约为125kB(fact: 当前平均block size只有~90kB)。

3. Blob-carrying transaction的价值和挑战

价值

可以把blob看成一种缓存,rollup 提交的交易数据从此以缓存的形式存在。降低对存储硬件的要求,为以太坊提供额外的数据扩容并降低 gas 成本。

挑战:对以太坊节点硬件性能的要求

Ethereum 当前平均区块大小只有~90kB,但是一个 blob 就有~125kB
根据 EIP-4844 的设计,每个slot正常情况下约为1 MB,因此每年增加的数据量为:
1 MB/block 5 block/min 43200 min/month * 12 month/year = 2.47 TB per year
每年增加的数据大小远超过了以太坊数据总量,这样的存储方案显然是不高效的。

解决方案

从短期扩容效果看,由于每个节点仍然需要储存全量历史数据,在实现 EIP-4844 的同时,对一段时间窗以外的 blob(具体的 limit time 还没有最终确定,可能是1个月或是1年)进行自动删除;

从 sharding 的长期利益看,实现EIP-4444,即节点不需要存储全量历史数据,而是只需要参照 history expiry,存储特定时间之后的数据;
这两种解决方案从不同程度上缓解了 blob-carrying transaction 在存储空间上的 tradeoff。

4. KZG Commitment

KZG Commitment是EIP-4844中采用的多项式承诺方案

解析 KZG commitment

KZG 是作者 Aniket Kate, Gregory M. Zaverucha和Ian Goldberg 姓氏的缩写,他们在2010年发表了多项式承诺方案论文“Constant-Size Commitments to Polynomials and Their Applications” ,并且这个方案在plonk-style的zk-snark协议中有很广泛的应用。

参考 Dankrad Feist 演讲中的示意图,KZG root 类似 Merkle root,区别在于 KZG root 承诺一个多项式,即所有 position 都在这个多项式上。基于 proto-danksharding 的场景,KZG root 承诺了一堆数据,其中的任何一个数据都可以被验证属于这个整体。

这也是为什么KZG commitment 在兼容性上对后面实现 DAS 更友好。

KZG commitment 的流程如下:

  • Prover:提供证明,计算 data 的 commitment,prover 无法改变给定的多项式,并且用于证明的commitment 只对当前这一个多项式有效;
  • Verifier:接收 prover 发送的 commitment value 并进行验证,确保 prover 提供了有效的证明。

KZG Commitment 的优势

我认为主要出于对成本和安全性的思考,可以归纳但不局限于以下几点:

成本
  • KZG commitment具备快速验证、复杂度相对更低、简洁的特点;
    • 不需要提交额外的proof,因此成本更低、更省 bandwidth;
  • 数据触达所需的 Point evaluation precompile可以获得更低的成本。
安全

假设出现了failure,也只会影响 commitment 对应的 blob 中的数据,而不会其他深远的影响。

更兼容

纵观sharding的整体方案,KZG commitment 对D AS 方案兼容,避免了重复开发的成本。

5. KZG Ceremony(trusted setup)

KZG Ceremony 是为KZG Commitment 提供 trust setup,目前吸引了超过20,000 participants 的参与和贡献,已经成为历史上最大规模的 trust setup。

最近社区参与热情高涨的KZG Ceremony就是为EIP-4844采用的KZG commitment提供trust setup;

KZG Ceremony的流程


参考Vitalik的流程图,任何人都可以作为participants贡献secret并与之前的结果进行混合产生一个新的result,以此类推,通过套娃的形式获得最终的SRS,并协助完成KZG commitment的trust setup

trust setup
  • EIP-4844中采用了一种常见的multi-participant trust setup,即powers-of-tau;
  • 遵循1-of-N 可信模型,不管多少人参与generating setup的过程,只要有一个人不泄漏自己的生成方式,可信初始化就是有效的;
必要性
  • KZG commitment的trust setup可以简单理解为:生成一个在每次执行cryptographic protocol时需要依赖的一个参数,类似于zk-snark需要可信初始化;
  • Prover在提供证明时,KZG commitment C = f(s)g1。其中f是评估函数,s就是KZG trusted setup最终获得的final secret;
  • 可以看出final secret是生成多项式承诺的核心参数,而作为获取这个核心参数的可信流程,这次KZG Ceremony对于整个sharding的实现非常重要。

6.EIP-4844带来的变化

Rollup

参考ethresear上给出的示意图,rollup需要将state delta、KZG commitment的versioned hash包含在calldata中进行提交(zk-rollup还需要提交zk proof)

可以发现不同的是,calldata只包含一些数据量小的,比如state delta、KZG commitment,而将包含大量交易数据的transaction batch放到了blob里。

  • 有效降低成本,放calldata里很贵;
  • 降低对区块空间的占用

安全性

  • Data availability: Blob存储在信标链上,等同于layer 1的安全性;
  • 历史数据:节点不会只会将blob存储一段时间,需要layer 2 rollup做永久数据存储,因此安全性依赖于rollup。

成本

Proto-Danksharding引入了新的交易类型,低成本数据格式blob的加入无疑会让rollup的成本进一步降低,取决于实际链上应用和实现进展,优化后rollup的成本可能降低x10甚至x50;

同时EIP-4844引入了blob fee;

  • Gas和blob将会分别有可调节的gas price和limit;
  • Blob的收费单元还是gas,gas amount随traffic变动,以此维持每个block平均挂8个blob的目标(限制额外增加的数据量)

Precompile的实现

Blob中的数据本身无法被直接触达,EVM只能获取data blob的commitment。因此需要rollup提供precompile来验证commitment的有效性.
下面分析两种EIP-4844中提到的precompile算法

Point evaluation precompile(对数学原理感兴趣参考vitalik的解析)
  • 证明多个 commitments 指向同一数据;
  • 主要针对 zk-rollup,rollup需要提供2种 commitments: 1. KZG commitment; 2. zk-rollup 本身的commitment;
  • 对于 optimistic rollup,大多数已经采用了multi-round fraud proof的机制,final round fraud proof 所需的数据量较小。因此,采用 point evaluation precompile 能达到更低的成本
Blob verification precompile
  • 证明 versioned hash 和blob 是有效对应的;
  • optimistic rollup 在提交欺诈证明时需要 access 全量数据,因此先验证 versioned hash 和 blob 合法,再进行fraud proof verification

三、Danksharding: 迈向full sharding的重要一步

Danksharding 的命名来自以太坊研究员Dankrad Feist

1. 扩容:进一步扩充Rollup的缓存空间

在proto-danksharding 实现后,由于新的交易格式引入了 blob,每个区块可以平均额外获得1MB的缓存空间。Danksharking 实现后,每个区块额外16MB,最大允许32MB。

2. Data availability:存储和验证策略更高效

相比proto-danksharding要求全节点下载全量数据,Danksharking 实现后以太坊节点只需要对blob抽样。Sampling 后的数据会分布在全网节点中,并可以组成完成的data blob。

DAS:高效抽样检查

通过纠缠码(erasure coding)帮助全网节点在下载部分数据的情况下更容易发现原始数据的丢失的概率,从而提升安全性

3.安全性:基本不变

由于每个节点不再保存全量历史数据,从数据可用性、备份和抽样检查的角度出发,安全性由至少一个节点保存全量数据变为多个节点存储部分数据,并最终还原完整数据。

虽然乍一看对单点的依赖安全性远高于对多点的依赖,但是以太坊网络中的节点数量够多,完全可以保证数据备份的需求,因此安全性并不会有很大变化。

4. 新的挑战:对 block builder 的要求提升

虽然验证者不需要下载并保留全量历史数据,对 bandwidth 和存储硬件的要求得到了缓解,但是区块创建者仍然需要上传包含全量 transaction data 的 blob 数据。


这里简单介绍一下 PBS(proposer/builder separation),参考 Dankrad 给出 PBS 在 danksharding 方案中的应用图:
将负责出块负责人的角色拆分为 proposer和 builder。最初是为了做anti-MEV设计的提案,在danksharding 的设计中为了降低区块创建时对bandwidth的要求。

四、其他sharding方案:Shardeum的动态分片

Shardeum 是 EVM 兼容的 layer 1公链, 与以太坊的 static sharding 方案不同,shardeum 通过dynamic state sharding的方案提升底层可扩展性和安全性,同时,天然地保证较高的去中心化程度;

1. Dynamic state sharding

优势

Dynamic state sharding 带来最直观的优势在于linear scaling,接入网络的节点可以非常高效的被sharding 算法动态分组,并快速响应,提升区块链网络的 TPS。在 dynamic state sharding 的设计中,每个节点会覆盖不同 range 的 address ,并且覆盖范围又会有冗余设计以保证高效的 sharding 和安全性。

场景内实现

抛开生态的复杂度,单从两种技术实现的角度看,dynamic state sharding 的难度大于 static sharding。可以看出Shardeum的技术团队在 sharding 的技术层面有很深的积累,团队之前在 Shardus technology上的研发也对这条公链的底层技术做出了很大贡献,甚至在项目早期阶段很好的展示了dynamic state sharding 所带来的 linear scaling。

2. Shardeum综合归纳

产品

将节点划分到不同的group,参考 divide and conquer 的思路,把计算和存储的 workload 进行分流,从而允许更高程度的并行处理。因此,可以容纳更多节点加入,进一步提升公链的 throughput 和去中心化程度。

团队

市场经验丰富,叙事能力超强,对动态分片研究很深。

技术

针对自己的场景设计了合适的sharding方案( dynamic state sharding )和共识的设计( Proof of Stake+ Proof of Quorum ),以提升可扩展性为第一目标,保证更高程度的去中心化和安全性。

进度

将在2023-02-02 launch betanet,值得关注。

五、对sharding的思考和展望

  • Sharding 是以太坊扩容的长久之计,也是一个价值巨大、意义深远的长期方案。实现 sharding 的过程中,现有所有方案都可能被不断迭代,包括现在提到的 proto-danksharding、danksharding 等,值得持续关注;
  • 对 sharding 大方向的理解很重要,但是每一个实现 full sharding 过程中的提案所采纳的技术方案(PBS、DAS、multidimensional fee market)同样值得关注,并且我相信也会涌现很多相关的优秀团队和项目;
  • Sharding 是对一种扩容技术的统称,但具体落地的方案并不是只有一种。需要认识到不同的公链会有适合自己场景的sharding方案。比如danksharding中的一些设计也只适合以太坊网络,安全性的tradeoff需要大量的节点来抵消;
  • Sharding 和其他扩容方案的合理结合对于可扩展性的提升1+1>2。目前的 Danksharding 并不是自成一派的扩容方案,而是和以太坊生态的其他实现相辅相成的。比如 Danksharding 和 rollup 一起,为以太坊扩容达成更好的效果。

Reference

https://notes.ethereum.org/@dankrad/kzg_commitments_in_proofs
https://notes.ethereum.org/@dankrad/new_sharding
https://vitalik.ca/general/2022/03/14/trustedsetup.html
https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#Why-use-the-hash-of-the-KZG-instead-of-the-KZG-directly
https://ethresear.ch/t/easy-proof-of-equivalence-between-multiple-polynomial-commitment-schemes-to-the-same-data/8188
https://dankradfeist.de/ethereum/2020/06/16/kate-polynomial-commitments.html
https://notes.ethereum.org/@dankrad/new_sharding
https://eips.ethereum.org/EIPS/eip-4844
https://www.eip4844.com/
https://biquanlibai.notion.site/Data-Availability-caa896aae59d489b98f2448f17b01640
https://docs.google.com/presentation/d/1-pe9TMF1ld185GL-5HSWMAsaZLbEqgfU1sYsHGdD0Vw/edit#slide=id.g1150d91b32e_0_474
https://ethresear.ch/t/a-design-of-decentralized-zk-rollups-based-on-eip-4844/12434
原文链接:https://mp.weixin.qq.com/s/45y2L25XwZBVHKzMsAxxrA
转载:https://learnblockchain.cn/article/5359

合约安全 - 选择器碰撞

这一讲,我们将介绍选择器碰撞攻击,它是导致跨链桥 Poly Network 被黑的原因之一。在2021年8月,Poly Network在ETH,BSC,和Polygon上的跨链桥合约被盗,损失高达6.11亿美元。这是2021年最大的区块链黑客事件,也是历史被盗金额榜单上第2名,仅次于 Ronin 桥黑客事件。

选择器碰撞

以太坊智能合约中,函数选择器是函数签名 "()" 的哈希值的前4个字节(8位十六进制)。当用户调用合约的函数时,calldata的前4字节就是目标函数的选择器,决定了调用哪个函数。如果你不了解它,可以阅读WTF Solidity极简教程第29讲:函数选择器

由于函数选择器只有4字节,非常短,很容易被碰撞出来:即我们很容易找到两个不同的函数,但是他们有着相同的函数选择器。比如transferFrom(address,address,uint256)和gasprice_bit_ether(int128)有着相同的选择器:0x23b872dd。当然你也可以写个脚本暴力破解。

大家可以用这两个网站来查同一个选择器对应的不同函数:

  1. https://www.4byte.directory/
  2. https://sig.eth.samczsun.com/

你也可以使用下面的Power Clash工具进行暴力破解:

  1. PowerClash: https://github.com/AmazingAng/power-clash

相比之下,钱包的公钥有256字节,被碰撞出来的概率几乎为0,非常安全。

漏洞合约例子

漏洞合约

下面我们来看一下有漏洞的合约例子。SelectorClash合约有1个状态变量 solved,初始化为false,攻击者需要将它改为true。合约主要有2个函数,函数名沿用自 Poly Network 漏洞合约。

  1. putCurEpochConPubKeyBytes() :攻击者调用这个函数后,就可以将solved改为true,完成攻击。但是这个函数检查msg.sender == address(this),因此调用者必须为合约本身,我们需要看下其他函数。

  2. executeCrossChainTx() :通过它可以调用合约内的函数,但是函数参数的类型和目标函数不太一样:目标函数的参数为(bytes),而这里调用的函数参数为(bytes,bytes,uint64)。

    contract SelectorClash {
     bool public solved; // 攻击是否成功
    
     // 攻击者需要调用这个函数,但是调用者 msg.sender 必须是本合约。
     function putCurEpochConPubKeyBytes(bytes memory _bytes) public {
         require(msg.sender == address(this), "Not Owner");
         solved = true;
     }
    
     // 有漏洞,攻击者可以通过改变 _method 变量碰撞函数选择器,调用目标函数并完成攻击。
     function executeCrossChainTx(bytes memory _method, bytes memory _bytes, bytes memory _bytes1, uint64 _num) public returns(bool success){
         (success, ) = address(this).call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_bytes, _bytes1, _num)));
     }
    }

攻击方法

我们的目标是利用executeCrossChainTx()函数调用合约中的putCurEpochConPubKeyBytes(),目标函数的选择器为:0x41973cd9。观察到executeCrossChainTx()中是利用_method参数和"(bytes,bytes,uint64)"作为函数签名计算的选择器。因此,我们只需要选择恰当的_method,让这里算出的选择器等于0x41973cd9,通过选择器碰撞调用目标函数。

Poly Network黑客事件中,黑客碰撞出的_method为 f1121318093,即f1121318093(bytes,bytes,uint64)的哈希前4位也是0x41973cd9,可以成功的调用函数。接下来我们要做的就是将0x41973cd9转换为bytes类型:0x6631313231333138303933,然后作为参数输入到executeCrossChainTx()中。executeCrossChainTx()函数另3个参数不重要,都填 0x 就可以。

Remix演示

  1. 部署SelectorClash合约。
  2. 调用executeCrossChainTx(),参数填0x6631313231333138303933,0x,0x,0x,发起攻击。
  3. 查看solved变量的值,被修改为ture,攻击成功。

总结

这一讲,我们介绍了选择器碰撞攻击,它是导致跨链桥 Poly Network 被黑 6.1 亿美金的的原因之一。这个攻击告诉了我们:

  • 函数选择器很容易被碰撞,即使改变参数类型,依然能构造出具有相同选择器的函数。
  • 管理好合约函数的权限,确保拥有特殊权限的合约的函数不能被用户调用。

转载:https://mirror.xyz/wtfacademy.eth/5rwcsBZzphdlKZj4MoIpn8aqwQ1MzQ8qy50ZEGNU_HU

接收ETH receive和fallback

回调函数

Solidity支持两种特殊的回调函数,receive()和fallback(),他们主要在两种情况下被使用:

  • 接收ETH
  • 处理合约中不存在的函数调用(代理合约proxy contract)

我们这一讲主要介绍接收ETH的情况。

接收ETH函数 receive

receive()只用于处理接收ETH。一个合约最多有一个receive()函数,声明方式与一般函数不一样,不需要function关键字:receive() external payable { ... }

receive()函数不能有任何的参数,不能返回任何值,必须包含external和payable。

当合约接收ETH的时候,receive()会被触发。receive()最好不要执行太多的逻辑因为如果别人用send和transfer方法发送ETH的话,gas会限制在2300,receive()太复杂可能会触发Out of Gas报错;如果用call就可以自定义gas执行更复杂的逻辑(这三种发送ETH的方法我们之后会讲到)。

我们可以在receive()里发送一个event,例如:

// 定义事件
event Received(address Sender, uint Value);
// 接收ETH时释放Received事件
receive() external payable {
    emit Received(msg.sender, msg.value);
}

有些恶意合约,会在receive()函数嵌入恶意消耗gas的内容,使得一些退款合约不能正常工作:Akutar NFT项目因此被永久锁定了11539 ETH,接近2亿元!因此写包含退款等逻辑的合约时候,一定要注意这种情况。

回退函数 fallback

fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。fallback()声明时不需要function关键字,必须由external修饰,一般也会用payable修饰,用于接收ETH:fallback() external payable { ... }。

我们定义一个fallback()函数,被触发时候会释放fallbackCalled事件,并输出msg.sender,msg.value和msg.data:

// fallback
fallback() external payable{
    emit fallbackCalled(msg.sender, msg.value, msg.data);
}

receive和fallback的区别

receive和fallback都能够用于接收ETH,他们触发的规则如下:
触发fallback() 还是 receive()?

           接收ETH
              |
         msg.data是空?
            /  \
          是    否
          /      \
receive()存在?   fallback()
        / \
       是  否
      /     \
receive()   fallback()

简单来说,合约接收ETH时,msg.data为空且存在receive()时,会触发receive();msg.data不为空或不存在receive()时,会触发fallback(),此时fallback()必须为payable。

receive()和payable fallback()均不存在的时候,向合约发送ETH将会报错。

总结

这一讲,我介绍了Solidity中的两种特殊函数,receive()和fallback(),他们主要在两种情况下被使用,他们主要用于处理接收ETH和代理合约proxy contract。

转载:https://mirror.xyz/wtfacademy.eth/EroVZqHW1lfJFai3umiu4tb9r1ZbDVPOYC-puaZklAw

Solidity ABI编码解码

ABI (Application Binary Interface,应用二进制接口)是与以太坊智能合约交互的标准。数据基于他们的类型编码;并且由于编码后不包含类型信息,解码时需要注明它们的类型。

Solidity中,ABI编码有4个函数:abi.encode, abi.encodePacked, abi.encodeWithSignature, abi.encodeWithSelector。而ABI解码有1个函数:abi.decode,用于解码abi.encode的数据。这一讲,我们将学习如何使用这些函数。

ABI编码

我们将用编码4个变量,他们的类型分别是uint256, address, string, uint256[2]:

uint x = 10;
address addr = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
string name = "0xAA";
uint[2] array = [5, 6];

abi.encode

将给定参数利用ABI规则编码。ABI被设计出来跟智能合约交互,他将每个参数转填充为32字节的数据,并拼接在一起。如果你要和合约交互,你要用的就是abi.encode。

 function encode() public view returns(bytes memory result) {
     result = abi.encode(x, addr, name, array);
 }

编码的结果为

0x000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000

由于abi.encode将每个数据都填充为32字节,中间有很多0。

abi.encodePacked

将给定参数根据其所需最低空间编码。它类似 abi.encode,但是会把其中填充的很多0省略。比如,只用1字节来编码uint类型。当你想省空间,并且不与合约交互的时候,可以使用abi.encodePacked,例如算一些数据的hash时。

 function encodePacked() public view returns(bytes memory result) {
     result = abi.encodePacked(x, addr, name, array);
 }

编码的结果为

0x000000000000000000000000000000000000000000000000000000000000000a7a58c0be72be218b41c608b7fe7c5bb630736c713078414100000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006

由于abi.encodePacked对编码进行了压缩,长度比abi.encode短很多。

abi.encodeWithSignature

与abi.encode功能类似,只不过第一个参数为函数签名,比如"foo(uint256,address)"。当调用其他合约的时候可以使用。

 function encodeWithSignature() public view returns(bytes memory result) {
     result = abi.encodeWithSignature("foo(uint256,address,string,uint256[2])", x, addr, name, array);
 }

编码的结果为

0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000

等同于在abi.encode编码结果前加上了4字节的函数选择器。

abi.encodeWithSelector

与abi.encodeWithSignature功能类似,只不过第一个参数为函数选择器,为函数签名Keccak哈希的前4个字节。

function encodeWithSelector() public view returns(bytes memory result) {
    result = abi.encodeWithSelector(bytes4(keccak256("foo(uint256,address,string,uint256[2])")), x, addr, name, array);
}

编码的结果为

0xe87082f1000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000007a58c0be72be218b41c608b7fe7c5bb630736c7100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000043078414100000000000000000000000000000000000000000000000000000000

与abi.encodeWithSignature结果一样

ABI解码

abi.decode

abi.decode用于解码abi.encode生成的二进制编码,将它还原成原本的参数。

function decode(bytes memory data) public pure returns(uint dx, address daddr, string memory dname, uint[2] memory darray) {
    (dx, daddr, dname, darray) = abi.decode(data, (uint, address, string, uint[2]));
}

我们将abi.encode的二进制编码输入给decode,将解码出原来的参数:

总结

在以太坊中,数据必须编码成字节码才能和智能合约交互。这一讲,我们介绍了4种abi编码方法和1种abi解码方法。

转载自:https://mirror.xyz/wtfacademy.eth/jXJnvwkoQzvJaqVIxagxneSZim6Qxm-StuNNxLuKuw8

实例验证 - call delegatecall

实例验证下call,delegatecall,两种方式下msg.sender和数据storage存储位置的区别

合约A

pragma solidity ^0.8.19;

contract A {
    address public temp1;
    uint256 public temp2;

    function three_call(address addr) public {
        (bool success, bytes memory result) = addr.call(
            abi.encodeWithSignature("test()")
        ); // 1
        require(success, "The call to B contract failed");
    }

    function three_delegatecall(address addr) public {
        (bool success, bytes memory result) = addr.delegatecall(
            abi.encodeWithSignature("test()")
        ); // 2
        require(success, "The delegatecall to B contract failed");
    }
}

合约B

pragma solidity ^0.8.19;

contract B {
    address public temp1;
    uint256 public temp2;

    function test() public  {
        temp1 = msg.sender;
        temp2 = 100;
    }
}

call 测试

B合约地址:0x086866663330344C7D1C51Bf19FF981AF3cB5782
A合约地址:0x05715D87C062B9685DD877d307b584bAbec964Ed
交易发起地址:0x6BC0E9C6a939f8f6d3413091738665aD1D7d2776

执行前数据

A B
temp1 0x0000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000
temp2 0 0

A合约执行three_call,参数为B合约地址
执行后数据

A B
temp1 0x0000000000000000000000000000000000000000 0x05715D87C062B9685DD877d307b584bAbec964Ed
temp2 0 100

delegatecall 测试

B合约地址:0x27153EDA2E085534811b040f6062f6528D6B80a1
A合约地址:0xd3C23F354Ca2160E6dC168564AB8954146cF35C9
交易发起地址:0x6BC0E9C6a939f8f6d3413091738665aD1D7d2776

执行前数据

A B
temp1 0x0000000000000000000000000000000000000000 0x0000000000000000000000000000000000000000
temp2 0 0

A合约执行three_delegatecall,参数为B合约地址
执行后数据

A B
temp1 address: 0x6BC0E9C6a939f8f6d3413091738665aD1D7d2776 0x0000000000000000000000000000000000000000
temp2 100 0

总结

  • A发起地址->B合约call->C合约
    msg.sender为B合约地址,非原始A发起地址,数据保存在C合约中
  • A地址->B合约delegatecall->C合约
    msg.sender为A发起地址,数据保存在B合约中

注意

callcode 已经在solidity 0.5+废弃,被delegatecall取代,故不做分析测试

TypeError: "callcode" has been deprecated in favour of "delegatecall".

参考
https://hicoldcat.com/posts/web3/senior-track-5/