NEO C# 合约编译器原理解析
Posted NEL新经济实验室
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NEO C# 合约编译器原理解析相关的知识,希望对你有一定的参考价值。
NEO合约编译过程牵涉到几个项目
neo-compiler下的neon项目负责code码转换
neo-devpack-dotnetx下的Neo.SmartContract.Framework负责公共接口定义
neo项目实现了framework中的接口
原理
c#的版本很多,从framework2.0到core2.3版本,语法差异很大,但是底层对应MSIL字节码没有变化,Neo的原理是先使用对应的编译器生成MSIL字节码,再把MSIL字节码转换成NEO vm的code码序列。这样做的好处在与利用了C#现有的语法成果,不必自己在设计一门语言,减少了合约编写的门槛。
大道不过三两行,说穿不值一文钱。下面我会通过一个完整的例子来说明这个流程,希望能藉此帮助更多人了解其中的原理细节,写出效率更好,费用更低的合约。
代码是在github上面找的,NEO-NEP5.1是NEP5的一个token,包含常用的元素,字段,事件与函数。具有常见的数据存储,合约调用及日志信息功能。
代码生成
c# -> MSIL
源码大致结构:实现细节摘除了,需要的请点击上面的链接
编译
编译完成后使用工具查看dll的类布局,其中字段还原没有问题,多了个类构造和构造函数,还有event对应出来的两个add/remove方法,后来在转换过程中都需要清除掉的.事实上在neo中event的更多的只是起到了标识的作用。具体的MSIL CODE太多就不贴上来了,下面提到哪里就贴到哪里.如果需要完整的文件,这里推荐一个常用工具ildasm,用来查看dll的语言信息十分方便。
MSIL -> 合约字节码
MSIL转换合约字节码工具是在neo-compiler/neon中定义的,转换命令为
这里可能会报错
这个错误估计是底下的库无法正确处理Action导致,这里手动定义下事件,改变下原来Transfer的定义,熟悉C#语法的人应该知道这两种写法几乎是等同的。
修改完成后重新执行命令‘dotnet . eon.dll NEP.dll’。看到如下字样即是成功的转换了类库,此时在运行目录下可以看到一个NEP.avm和一个NEP.abi的文件,前者包含了运行所需的字节码,后者仅仅描述了方法和事件信息。
到这neo字节码生成完毕,然后就按官方的方法把这个vm文件发布出去。
代码调用
合约代码入口就是文件的main函数,通常是根据传入的函数名称判断调用到对应的工作函数。下面会通过两个具体函数的执行过程,通过对比三种代码来说明这个编译执行的过程。
部署合约函数流程
入口跳转
c#
MSIL
VM OPCODE
其中主要看两条指令,一个是IL_0081/013A,一个是IL_008c/014B,前者就是判断参数是不是depoly,后者是判断成功后进行函数跳转,调用需要执行的函数。
调用部署函数
c#
MSIL
VM OPCODE
几个重要的函数调用位置对比:
检查合约所有人
错误提示:You are not the Owner of this Smart Contract
获取当前合约币供应量totalSupply
错误提示:防止重复调用
设置初始供应量
打印日志
转账合约函数调转逻辑
入口跳转
c#
MSIL
VM OPCODE
指令IL_0233/03E3用于确认要调用的函数是否是transfer,IL_0274/0477 用于打印错误日志信息,指令IL_02a9/04DB调用转账函数。
调用转账函数
c#
MSIL
VM OPCODE
提示资产错误
打印输出日志
函数分类
当前使用的版本最常见的就是SYSCALL和CALL_I。
SYSCALL
用于调用外部接口函数,VM定义了一系列的外部接口,接口定义可以在Neo.SmartContract.Framework项目中查看,这些函数实现在Neo项目的SmartContract文件夹下面,函数使用SyscallAttribute标记,合约调用时候会按名索引到对应的执行函数。这里以Storage为例子说明这个问题。
定义
实现
StateMachine继承自neo-vmsrc eo-vmInteropService.cs文件中的InteropService,每次调用Register时,会在其中注册入实现函数,调用时通过名字查找到这个函数,在传入参数执行。
Storage_Put 具体实现
注册函数实现
注册函数
转换
转换主要是通过判断函数是否有SyscallAttribute来确定,如果是的会把函数名称及参数信息添加上去。
判断函数
转换Syscall
使用
CALL_I
用于合约内部函数调用
定义
在合约中自己定义的方法都符合这个类型,如例子中的转账及部署等。
转换
判断,如果在当前使用的合约机器类库中存在该实现,则确定为这种类型的调用
转换
使用
这种调用直接跳转到对应的指令位置执行。
日志信息
日志提供了一种方式用于我们观察判断合约执行的状态,日志有两种使用方式,一种通过event定义日志,另一种直接调用Neo.Runtime.Notify函数,实质上呢,前者经过一系列的转换步骤后也是转换到Neo.Runtime.Notify逻辑当中,Neo.Runtime.Notify是一种SysCall,最终在执行器中注册如具体的Notify通知实现,因而这里自讨论下event的方式。
定义
转换
因为C#的事件直接执行,实际到MSIL层转换成Invoke调用,其中更多细节可以去研究下C#Delegate,event的具体实现机制,此处不多赘言。
事件解析:解析出来的东西并没啥用,只要在到处json时能看到个事件项目,函数实际上还是去搜索
Invoke查找,之前有提过event和Nottify本质上是相同的,相同之处就体现在IsNotifyCall函数中,该函数会判断函数名称是否是Invoke,类型就粗暴的判断了Action。满足就是个NotifyCall了,返回的函数名就不用在意了,最后转换的时候给统统变成一个名字:Neo.Runtime.Notify。
转换
Neo.Runtime.Notify 定义
使用
c#中调用
Vm执行Neo.Runtime.Notify Syscall.
数据存储
Neo的数据存储主要通过Storage接口提供,外部实现的数据库存储,具体参考上面给的代码例子,Neo合约也不支持普通成员变量,但是静态成员是支持的,原因在于,静态成员编译即确定,在转换的过程中会在每个使用到静态变量的地方嵌入实际的值。
如果你对区块链有兴趣
想从一名开发者转型为区块链开发者
欢迎加入NEL开发者社区
在这里,你可以得到专业的区块链开发学习计划
NEL将不定期的在社区分享开发技术文章视频干货
也会不定期邀请区块链技术大咖进行线下面对面分享
我们会以线下讲座、线上直播、社区问答、文字介绍等形式推广NEO区块链平台
同时我们会发布一些简单又具有意义的开发任务帮助初学者练习提高
NEL是NEO生态的中国开发者社区。作为NEO区块链技术、智能合约技术推广的核心力量,致力于培养社区开发人才,推动项目落地,发展NEO社区生态。建立完整的社区人才发展体系,以适应不同层次的开发者参与区块链项目的需求。
NEL的使命是帮助优秀的区块链开发者抓住区块链技术所提供的难得机遇,实现开发者改变世界的理想,帮助开发者成功。
以上是关于NEO C# 合约编译器原理解析的主要内容,如果未能解决你的问题,请参考以下文章