为将傅恒与魏璎珞的爱情上链,作为技术小白的我读了EVM上百行代码,终于搞定了...

2023-11-23 08:50

本文主要是介绍为将傅恒与魏璎珞的爱情上链,作为技术小白的我读了EVM上百行代码,终于搞定了...,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

640?wx_fmt=jpeg

作者 | Vasa  TowardsBlockChain 联合创始人

编译 | kou、Guoxi



傅恒爱上了魏璎珞,我却因为他们的爱情学习了以太坊虚拟机!因为小编想要用在以太坊上将他们的爱情上链,学习以太坊,就要了解以太坊虚拟机!


以太坊虚拟机相对于以太坊就好比一枚优质的机芯成就一款精致的手表,它的出现让以太坊如虎添翼,从众多区块链平台中脱颖而出,开启了“区块链2.0”新时代。因此,深刻理解并熟练掌握以太坊虚拟机是以太坊开发者的基本功。


近日,MIT孵化初创公司TowardsBlockChain的联合创始人vasa对以太坊虚拟机概念以及以太坊虚拟机工作原理做了深入且透彻分析。在本文中,vasa将带你探讨以太坊虚拟机的核心细节,介绍如何创建智能合约,消息调用函数(message call)如何工作,以及与数据管理,比如存储,内存,calldata和堆栈相关的所有内容,满满的都是干货!



傅恒,放弃尔晴,这世间,难逃魏璎珞。


延禧攻略最近大火,傅恒和魏璎珞求而不得的爱情也令很多人觉得惋惜。那么傅恒到底为什么爱上魏璎珞呢?有网友真相了。


640?wx_fmt=jpeg


傅恒为什么爱魏璎珞?原来是因为周星驰《大话西游》中的副歌《一生所爱》。


傅恒放下身份,爱上魏璎珞,小编被感动了。


小编想把他们的爱情记录在区块链中,之前就看到过有人将爱情宣言永久记录在了以太坊区块链上,小编跃跃欲试,想要亲自试一下。想要学习以太坊,就要先了解什么是以太坊虚拟机。



透视以太坊虚拟机


在通过代码示例了解以太坊虚拟机工作机制之前,小编先通过3张图,带你了解以太坊虚拟机在以太坊中的位置以及组成部分。


下面这张图表展示了以太坊虚拟机在整个以太坊架构中的位置(红色框框),位于节点之上,代码之下。

640?wx_fmt=jpeg

接下来,你看到的是以太坊虚拟机架构,它是一个基于堆栈的简单架构。

640?wx_fmt=jpeg以太坊是一个基于堆栈的简单架构


接下来这张图,是以太坊的运行模型,它描述了以太坊虚拟机的不同组成部分之间如何互相作用。


640?wx_fmt=jpeg

通过这3张图表,相信你已了解以太坊虚拟机的“外表”,接下来,通过一些图件和实例代码带你探索以太坊虚拟机的内部奥秘。



以太坊智能合约


智能合约基础


要想探索以太坊虚拟机的内部奥秘,首先你需要了解什么是智能合约,通俗的讲,以太坊智能合约就是在以太坊虚拟机上运行的计算机程序。以太坊虚拟机是为以太坊智能合约在容器沙箱中运行而设计的一个完全隔离的环境。这意味着在以太坊虚拟机中运行的每个智能合约都无法访问托管虚拟机的计算机上运行的网络架构,文件系统或其他进程。


以太坊网络中有两种帐户:智能合约账户和外部拥有帐户。每个帐户都由一个地址来标识,所有的帐户共享相同的地址空间,即以太坊虚拟机接受长度为160位的地址。

640?wx_fmt=jpeg以太坊网络中的账户由一个160位的字符索引


每个帐户中都包含余额,随机数,字节码和存储的数据(简称存储,下同)四个部分。但这两种账户之间存在一些差异。例如,外部拥有帐户并没有代码部分和存储部分,而智能合约帐户的这两个部分分别存储它们的字节码和整个状态树的默克尔树( Merkle Tree)根哈希。此外,外部拥有帐户具有与其相应的私钥,而智能合约帐户却没有。智能合约帐户除了对每笔以太坊交易进行常规的密码学签名之外,所有的操作都由智能合约中的代码控制。

640?wx_fmt=jpeg

通过上图,你会了解到,外部拥有账户由私钥控制,且外部拥有账户中不包含以太坊虚拟机代码,而智能合约账户含有以太坊虚拟机代码,并由代码控制。


创建智能合约


在了解什么是智能合约后,接下来,通过代码实例教你如何创建智能合约。


创建智能合约的操作就是一笔简单的交易,在这笔交易中接收者地址为空,数据字段包含要创建的智能合约的编译字节码。 先看这个实例, 智能合约MyContract


640?wx_fmt=png


打开一个Truffle控制台(Truffle Console,Truffle是目前最流行的以太坊开发框架),运行truffle develop命令进入到开发者模式。进入后,按照以下指令部署智能合约MyContract


640?wx_fmt=png


通过运行以下代码检查智能合约是否已成功部署:


640?wx_fmt=png


这么一长串代码做了什么事情呢?


在将智能合约部署到以太坊区块链时,发生的第一件事是创建了智能合约账户。


以太坊黄皮书上写到,新创建智能合约帐户的地址为仅包含发件人和帐户随机数的数据经RLP(Recursive Length Prefix,递归长度前缀)编码并经Keccak哈希计算得到的哈希值的最右边160位。


正如你所看到的,上面示例的构造函数中记录了智能合约的地址。你可以通过检查receipt.logs [0] .data是否是填充32字节的智能合约地址以及receipt.logs [0] .topics是否是字符串“Log(address)”的keccak-256哈希值来确认。

640?wx_fmt=jpeg当你调用了一个智能合约中函数时,后台发生的操作


接下来,在交易中与智能合约一起发送的数据将被作为字节码执行。


这个操作将初始化存储中的状态变量,并确定正在创建的智能合约的正文。此过程在智能合约的生命周期内仅执行一次。初始化代码并不会被存储在智能合约中,实际上它执行的返回值也就是字节码才会被存在智能合约中。切记,智能合约一旦被创建,任何人都无法更改其代码。


由于智能合约的初始化过程会返回需要存储的智能合约正文的代码,因此从构造函数的逻辑上讲,代码是不可访问的。看一下智能合约Impossible代码:


640?wx_fmt=png


如果你尝试编译这个智能合约,你将收到一条警告,告诉你在构造函数中引用了this指针,但它还是会编译。


但是,如果你尝试部署一个新的智能合约实例,它将还原,这是因为尝试运行尚未存储的代码是没有意义的。


另一方面,我们能够访问智能合约的地址,因为智能合约帐户是存在的,但它里面还没有任何代码。


同时,代码的执行可以会产生其他事件,例如更改存储,创建更多帐户或进行进一步的消息调用。比如,下面这一段AnotherContract智能合约代码:  


640?wx_fmt=png


猜一下在Truffle控制台中编译它会发生什么。


640?wx_fmt=png


此外,还可以使用CREATE操作码来创建智能合约,这是Solidity语言新构造的编译操作码。两种智能合约创建方案有着相同的运行机制。


在深入解读以上三段智能合约代码之后,接下来,带你了解消息调用机制的工作原理。


消息调用机制


智能合约可以通过消息调用机制调用其他智能合约。


每当智能合约需要调用另一个智能合约的函数时,它都会通过生成一个消息调用。每个消息调用都有发送者,接收者,有效载荷,以太币传输数量和一定量的以太坊燃料。


消息调用的深度被限制为小于1024级。

640?wx_fmt=jpeg消息调用由调用命令触发,请求和返回值由内存传递


Solidity语言为地址类型提供了一个本地调用方法,其工作方式如下:


640?wx_fmt=png


这里的gas就是要发送的以太坊燃料数量,address是要调用的智能合约地址,value是要传输的以太币数量,以wei为单位,data是要发送的有效载荷。请记住,valuegas都是可选参数,但要小心的是,在低级别调用中默认是将发送者剩余燃料全部发送。      

640?wx_fmt=jpeg燃料开支图


每个智能合约都可以决定在消息调用时发送的燃料数量。


由于每次消息调用都可以以燃料耗尽(out-of-gas,OOG)结束,为了避免安全问题,发送方要至少保留剩余燃料的1/64。


通过这种机制,发送者可以避免出现内部燃料耗尽异常,确保在耗尽燃料之前完成智能合约的执行。


640?wx_fmt=jpeg以太坊虚拟机异常执行图


看一下下面这段代码:智能合约Caller


640?wx_fmt=png


智能合约Caller只有一个回退函数,可以将每个接收到的消息调用重定向到Implementation示例上,这个实例只是在每接收到一个消息调用时抛出一个assert(false),这将消耗完所有给定的燃料。然后,这里的想法是记录实施调用之前和之后Caller合约中燃料的数量。


打开一个Truffle控制台,看看会发生些什么。    


640?wx_fmt=png


如你所见,71495大约是4578955的1/64。这个例子清楚地表明了可以处理内部调用的燃料耗尽异常。


Solidity还提供以下操作码,允许使用内联汇编(inline assembly)管理消息调用:


640?wx_fmt=png


其中g是要发送的燃料量,a是要调用的地址,v是传输的以wei为单位的以太币数量,in表示insize字节的内存位置,被调用的信息就存储在这里,outoutsize表明消息调用的返回数据在内存中存储的位置。


唯一的区别就是使用内联汇编进行消息调用允许处理返回数据,而如果使用函数只返回1或0来代表调用是否失败。

640?wx_fmt=jpeg以太坊虚拟机可以通过消息调用来输入外部数据

以太坊虚拟机可以输出日志,也可以给调用智能合约输出返回值


以太坊虚拟机支持名为delegatecall的一个特殊的消息调用方法。也就是说除了内联汇编版本之外,Solidity语言还提供了内建地址方法。


它与低级消息调用的区别在于目标代码在消息调用智能合约中执行,并且msg.sendermsg.value不会更改。


通过下面实例来解读delegatecall方法的工作方式。


首先看一下智能合约Greeter


640?wx_fmt=png


通过上述代码,智能合约Greeter简单地声明了一个thanks函数,该函数发布一个包含msg.valuemsg.sender数据的事件。


通过在Truffle控制台中运行以下代码来尝试此方法:


640?wx_fmt=png


通过上面的代码已确认了它的功能,接下来再看一下智能合约Wallet


640?wx_fmt=png


这个智能合约仅定义了一个回退函数,该函数通过delegatecall消息调用执行Greeter#thanks方法。


那么,在智能合约Wallet中调用Greeter时会发生什么?


640?wx_fmt=png


你可能已经注意到,上面的代码中可以确认delegatecall函数保留了msg.valuemsg.sender的值。


这意味着智能合约可以在运行时从不同的地址动态加载代码。存储,当前地址和余额仍然指向调用智能合约,只有代码来自被调智能合约。这样使得可以在Solidity语言中实现类似程序库(library)功能。


关于delegatecalls消息调用,你还需要留意这一点,如上所述,调用合约的存储是由执行代码访问的。看一下智能合约Calculator


640?wx_fmt=jpeg


智能合约Calculator只有两个功能:addproductCalculator合约并不知道如何执行加法或乘法,需要时它将这些调用分别指派给AdditionProduct两个智能合约来执行。但是,所有这些智能合约共享相同的状态变量用以存储每次计算的结果。


通过以下代码,看它是如何工作的:


640?wx_fmt=png


通过上面的代码中可以确认正在使用Calculator合约的存储。


除此之外,还可以确认正在执行的代码存储在AdditionProduct两个智能合约中。


另外,对于调用函数,delegatecall有一个Solidity语言汇编操作码版本。


通过下面的几行Delegator智能合约代码你就可以学会如何去使用它:


640?wx_fmt=png


你需要使用内联汇编来执行delegatecall


你会注意到,这里没有值参数,因此msg.value并不会改变。也许你会有疑问,为什么要加载这个0x40地址? calldatacopycalldatasize是什么?你需要做的是在Truffle控制台上运行相同的命令来验证结果是否正确。


清楚了解delegatecall消息调用的工作方式十分重要。


每个触发的消息调用都将从当前智能合约发送,而不是被调用的智能合约。此外,执行代码可以读取和写入调用智能合约的存储。如果没有留意这些细节,即使是很小的错误也可能导致数百万的损失,比如,The DAO事件。


在了解以太坊智能合约的相关内容后,接下来,带你探索以太坊虚拟机的数据管理。



数据管理


以太坊虚拟机根据数据的内容,以不同的方式来管理不同类型的数据。


除了智能合约代码,至少可以区分出四种主要类型的数据:堆栈,calldata,内存和存储。接下来依次对它们做出分析。

640?wx_fmt=jpeg以太坊中的不同数据类型


堆栈


以太坊虚拟机是一个基于堆栈的机器,这意味着它不在寄存器上运行,而是在虚拟堆栈上运行。堆栈的深度上限为1024,堆栈项的大小为256位。事实上,以太坊虚拟机是一个256位的机器(这有利于Keccak256哈希计算和椭圆曲线计算)。堆栈是大多数操作码存储其参数的地方。

640?wx_fmt=jpeg所有的操作都是在堆栈上执行的

通过PUSH/POP/COPY/SWAP等操作来交互


以太坊虚拟机提供了许多操作码来直接对堆栈进行操作。其中包括:


  • POP 从堆栈中删除项目。

  • PUSHn 将以下n个字节的项目放在堆栈中,n的取值为1到32。

  • DUPn 复制第n个堆栈项目,n的取值为1到32。

  • SWAPn 交换第1和第n个堆栈项目,n的取值从1到32。


Calldata


calldata是一个只读的字节编址空间,其中保存交易或调用的数据参数。与堆栈不同,要使用calldata数据,你必须准确指出字节偏移量和要读取的字节数。


以太坊虚拟机提供的用于操作calldata的操作码包括:


  • CALLDATASIZE 指出交易数据的大小。

  • CALLDATALOAD 将32个字节的交易数据加载到堆栈中。

  • CALLDATACOPY 将多个字节的交易数据复制到内存中。


Solidity语言还提供这些操作码的内联汇编版本,它们分别是calldatasizecalldataloadcalldatacopycalldatacopy需要三个参数(t,f,s):它会将位置为f的calldata中的s个字节复制到位置为t的内存中。此外,Solidity语言允许你通过msg.data访问calldata。


看一下下面这段delegatecall的内联汇编代码:


640?wx_fmt=png


为了将调用指派给_impl这个地址,必须发送msg.data。由于delegatecall操作码与内存中的数据一起操作,你需要将calldata复制到内存中。这就是使用calldatacopy将所有calldata复制到一个内存指针的原因(注意使用到了参数calldatasize)。


接下来使用calldata来分析另一实例。


640?wx_fmt=png


将该内存指针存储在变量a中,并在b中存储a之后32字节的内容。然后使用calldatacopy将第一个参数存储在a中。


你会注意到正在从calldata中的第4个位置而不是从它的开头复制它。这是因为calldata的前4个字节包含被调函数的签名,在示例中为bytes4(keccak256(“add(uint256,uint256)”))。这是以太坊虚拟机用来识别哪一个是被调函数的原理。


然后,将第二个参数存储在b中,即复制calldata的后面的32个字节。最后,只需要从内存加载它们,并把两个值相加。


运行以下命令在Truffle控制台上自行测试:


640?wx_fmt=png


内存


内存是一个非永久性、可读写字节编址空间。它主要用于在执行期间存储数据,大部分情况下是将参数传递给内部函数。由于内存是非永久型的,每个消息调用都要从清空了的内存开始。即内存中所有位置被初始化为零。与calldata相比,内存可以在字节级别进行寻址,但一次只能读取32字节的字。

640?wx_fmt=jpeg内存以线性排列,可以被字节级别的索引

通过MSTORE/MSTORE/MLOAD指令来交互

内存中的所有位置都被初始化为0


当你往内存中写入以前没有使用过的数据时,内存就“增加”了。除了写入本身的成本之外,这种增加也有成本,它在前724字节时线性增加,之后以二次方增加。


以太坊虚拟机提供三个操作码用于与内存区域的交互:


  • MLOAD 将一个字从内存加载到堆栈中。

  • MSTORE 将一个字保存到内存中。

  • MSTORE8 将一个字节保存到内存中。


Solidity语言还提供这些操作码的内联汇编版本。


只看上面内容就可以了吗?不!你还需要了解关于内存的另一个关键事项。


Solidity总是在0x40位置存储着一个空闲的内存指针,指向内存中第一个未使用的字,这就是你加载这个字来操作内联汇编的原因。


由于内存最前面的64字节是为以太坊虚拟机保留的,因此可以确保操作不会覆盖Solidity内部使用的内存。


例如,在上面给出的消息调用delegatecall示例中,加载这个指针用以存储给定的calldata并发送它。这是因为内联汇编操作码delegatecall需要从内存中获取其有效载荷。


另外,如果你注意Solidity编译器输出的字节码,你会发现它们都以0x6060604052 ...开头,即:


  • PUSH1:以太坊虚拟机操作码为0x60

  • 0x60:空闲的内存指针

  • PUSH1:以太坊虚拟机操作码为0x60

  • 0x40:空闲内存指针的内存位置

  • MSTORE:以太坊虚拟机操作码是0x52


在汇编级别使用内存时必须非常小心。否则,你可能会覆盖已使用的内存空间。


存储


存储是一个永久的、可读写字节编址空间,它是每个智能合约存储其永久信息的地方。与内存不同,存储是一个永久性区域,只能通过字来索引。它是256位到256位的键值映射。


一个智能合约除了本身的存储外,既不能读取也不能写入其他任何智能合约的存储,和内存一样,存储中所有位置都被初始化为零。


将数据保存到存储中是以太坊虚拟机耗费燃料值最高的几个操作之一。


这笔燃料费用并不总是相同的。就比如将存储中一个值从零修改为非零值需要20000单位的燃料,而存储同样的非零值或将这个非零值设置为零时只需要5000单位。


以太坊虚拟机提供两个操作存储的操作码:


  • SLOAD 将存储中的字加载到堆栈中。

  • SSTORE 将一个字保存到存储中。


Solidity语言的内联汇编也支持这些操作码。


Solidity会将智能合约中每个已定义的状态变量自动映射到存储中的相应位置。策略非常简单,对于静态大小的变量,也就是除了映射和动态数组之外的所有变量在从位置0开始的存储中连续分布。


对于动态数组,位置(p)存储动态数组的长度,其数据将位于由哈希计算p(keccak256(p))产生的位置。对于映射,该位置并未使用,并且对应于键k的值将位于哈希计算keccak256(k,p)产生的位置。哈希计算keccak256kp)的参数始终需要填充为32个字节。


通过Storage智能合约代码了解它是如何工作的:


640?wx_fmt=png


打开一个Truffle控制台来测试它的存储架构,首先编译并创建一个新的智能合约实例:


640?wx_fmt=png


然后,可以确定地址0保存数字2,地址1保存智能合约的地址:


640?wx_fmt=png


检查存储位置2是否保存数组的长度,如下所示:


640?wx_fmt=png


最后,检查存储位置3是否未使用,并且键值对映射的值是否保存在上文中所说的位置:


640?wx_fmt=png


通过以上图表和详细的代码实例,你是否像小编一样很好地理解以太坊虚拟机?接下来,为了将傅恒与魏璎珞的爱情上链,小编会继续学习以太坊!


最新热文:

  • 币价涨涨跌跌,程序员竟如此佛系... | 他说

  • 为什么区块链开发者工资这么高?看看他们需要掌握多少东西就知道了

  • 性能赶超EOS?一文带你深挖并发执行模型

  • Coinbase 交易所背后,究竟是一支怎样的团队?


640?wx_fmt=jpeg

大力戳↑↑↑  加入区块链大本营读者⑦号群

(群满加微信 qk15732632926 入群)


内容转载请联系微信:qk15732632926

商务合作请联系微信:fengyan-1101



640?wx_fmt=jpeg

640?wx_fmt=jpeg

这篇关于为将傅恒与魏璎珞的爱情上链,作为技术小白的我读了EVM上百行代码,终于搞定了...的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/416747

相关文章

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

D4代码AC集

贪心问题解决的步骤: (局部贪心能导致全局贪心)    1.确定贪心策略    2.验证贪心策略是否正确 排队接水 #include<bits/stdc++.h>using namespace std;int main(){int w,n,a[32000];cin>>w>>n;for(int i=1;i<=n;i++){cin>>a[i];}sort(a+1,a+n+1);int i=1