
No.10 智能合约工作流初探
上一篇讲到Remix,接下来我们具体看看如何编译、部署、测试智能合约。
编译 打开实时编译auto compile,无需多余的操作,注意下面提示框即可,红色存在错误,绿色代表无语法错误。
Remix已经识别到代码中智能合约Car。

切换到Run选项卡,分为4个小区域:

为了部署我们自己的智能合约,必要操作步骤如下:
1、在区域 1,运行环境 Environment 中选择 Javascript VM,即把合约部署到运行在浏览器内存中的测试网络,既然是运行在浏览器内存中的网络,页面刷新的话数据将会被重置,但是如果我们部署在主域 Rinkeby 测试网络上,数据就不会丢失;
2、在区域 2,initialBrand 的输入框里面填入 "AUDI",注意填入的内容必须包含两边的引号,没有引号会导致错误,这是很多新同学会碰到的问题,实际上你可以理解为参数必须是 JSON.stringify 后的结果; 在区域 2,单击
"Create",部署新的智能合约实例;

合约部署完成之后,在最下面能看到新的合约账户,账户下面有 3 个按钮,每个按钮代表 1 个可以调用的合约函数,如 getBrand,如果按钮旁边有输入框,表示调用该函数的时候需要输入参数,如 setBrand。我们直接点击 brand 或者 getBrand 按钮,会看到按钮右边立即显示了部署合约时设定的 AUDI,如果不小心点击了 setBrand,却没有在点击之前在其右边的输入框里面填入任何内容,恭喜你,智能合约中的 brand 属性现在已经被置空了,这也是 Remix 的一个大坑:不做参数校验,并且即使失败也是悄悄的失败。如何正确更新 brand 属性的值呢?需要先在 setBrand 右侧的输入框里填入 "BMW",同样包含两边的引号,然后单击 setBrand 按钮。
可能细心的同学已经注意到了,我们合约里面没有声明 brand 方法,但部署后的合约里面有,这就是 Solidity 为可公共访问的存储型变量生成的 getter 方法,实际上我们源代码中的 getBrand 方法可以直接删掉的。
纸上得来终觉浅,学到不如做到!期望读到这里的你已经把 Car 智能合约的代码输入到 Remix,并且部署自己的合约实例,测试了合约函数,有没有发现什么问题?下章让我们深入聊聊智能合约相关的各种问题。
No.11 智能合约工作流再探
合约部署(Create)和合约实例(Contract Instance)里面有太多的细节需要扒清楚,才能达到知其然且知其所以然的理解深度。比如下面几个问题对我们的理解非常关键:
1、智能合约部署的时候到底发生了什么事情?发送了什么数据?
2、部署合约的时候到底用的哪个账户?消耗的汽油从哪里来?
3、合约账户的形式是什么样的?和普通账户有没有区别?
4、新创建合约实例上的 3 个方法对应的按钮,为啥 setBrand 是红色而 getBrand 和 brand 是蓝色的?两种到底有啥区别?对后续的 DApp 开发有什么影响?
5、前面章节中我们给 Metamask 账户充值的时候讲到以太坊单笔交易确认时间平均在 15s 左右,为什么 Remix 测试时候那么快?几乎是马上就能有结果?
6、如果合约源代码修改了,我们能否直接在老的合约实例上进行测试?如果不能是为什么?要测试新合约该怎么做?
7、合约编译的时候虽然没有报红色的错误,但是报了蓝色的 Warning,那是什么原因?该怎么修复? 只要把上面这些问题逐个搞明白,我们在智能合约部署、合约函数调用等方面就能毕业了,哈哈!
合约部署的本质
前面提到合约部署实际上是发起了 1 笔交易,交易的接收者为空,以太坊将接收者为空的交易默认为是合约实例创建请求,这类交易中会携带当前部署合约的机器码(ByteCode),部署成功的话会返回新建的合约账户,合约部署本身需要消耗 Gas,这个费用是发起者支付的。合约部署的交易细节再 Remix 上通过调试日志可以一览无余,打开 Remix 的调试日志区域,通过点击合约创建日志右上角的 Details 按钮,展开交易细节,如下图:

其中 contractAddress 是交易的返回结果,而不是携带在交易中的信息。此外,可以看到交易中携带的合约机器码是十六进制串,如果想查看某个合约编译后的机器码,可以直接在 Compile 目录下点击 Details 按钮,如下图:

BYCODE是否相同?
汽油从哪里来?
细心的同学可能观察到了,上面合约部署交易中的 from 字段 0xca35b7d915458ef540ade6068dfe2f44e8fa733c 跟右侧调试区域 Javascript VM 下面出现的 Account 列表中的第一个账户完全相同(核对前后各 5 位即可),余额比其他账户少一点点。

合约账户的本质
合约部署的直观结果是我们得到了一个账户,但是我们仅仅有这个账户的地址,而没有账户的公钥、私钥,这就是合约定义:contract instance is an account controlled by code 的准确含义,后续和合约的交互也只能通过代码,即智能合约的函数调用。
Remix 带来的幻觉
在 Remix 上部署、测试智能合约,不论是 call 还是 transaction,速度都非常快,这是因为测试时以太坊网络运行在内存中,并且是单节点,速度自然很快,但是缺陷是每次页面加载,这个测试网络中的合约实例、合约中的数据都会丢失,网络中的账户及余额也会重建。
然而实际的以太坊网络,不论是测试网络还是主网,都不会这么快,原因在前面有讲,在分布式网络上达成共识确实是个很复杂的过程,尤其是对于目前使用 POW 共识算法的以太坊来说。
如何重新发布?
前端工程师修改页面源代码后需要刷新浏览器,才能看到最新的页面效果,如果智能合约的源代码被修改,我们同样需要重新部署智能合约的示例,并测试这个全新的合约实例。与前端页面开发不同的是,刷新浏览器之后,老的页面的各种样式、DOM接口、应用状态都被销毁了,而以太坊上老的合约实例是不会被销毁的,即使部署了新的版本,如果你愿意也能和老版本继续交互。
Solidity 其实和 JS 区别很大
到目前为止,可能我们并没有看出 Solidity 和 JS 有太大差别,但实际上差别是非常大的,得从 Remix 里 Compile 功能给出的蓝色提示说起,如果双击蓝色 Warning 提示,Remix 会跳转到 Analysis 选项卡,滚到页面底部我们能看到 Warning 的详细说明,如下图:

Car 合约自动生成的 brand 函数可能会消耗无限的汽油,这是在智能合约设计时需要避免的,因为智能合约中的任何代码都是需要消耗汽油的,如果不加限制,很容易出现因为汽油不够而操作被回滚、或调用失败的情况。
具体到我们的例子,brand 属性为字符串,在静态类型语言中字符串本质是动态长度的字节数组(dynamically-sized byte array),也就是说我们的 brand 属性是可能是非常长的字符数组,如果长度超过某个临界值,就会消耗超出预期的汽油,具体到我们的例子,只需要把 brand 的类型从 string 设置为 bytes32 即可。
这里就引申出一个问题,Solidity 中的变量类型和 Javascript 差别就比较大,虽然都可划分为基本数据类型和引用数据类型两大类。
只谈区块链不谈币--No.12 智能合约工作流初悟
调用合约函数时到底发生了什么?
上面提到 Remix 渲染出来的智能合约函数被用颜色标记成了两类,如果鼠标在两类函数的按钮上停留 2s 钟,相信你会觉察到两者的区别(浏览器弹出的 title 提示),如下面两张图:
setBrand 被标记为红色,调用它实际发起的是 transaction,任何交易都是全异步的。

而 getBrand 被标记为蓝色,调用它就是简单的函数调用:call。

智能合约中的函数要么是 transaction 类型,要么是 call 类型,两种类型函数的本质都是接口请求,但是又存在很大的区别,对比如下表:

对于前端工程师来说,如果用同步、异步函数来类比则很好理解(严格来说这个类比不是非常恰当): 合约中的纯粹函数调用相当于是同步的,而通过交易去调用的函数相当于是异步的;
通常我们不指望异步函数马上返回结果,除非给他传入了回调,这也就预示着智能合约中会修改合约数据的函数即使声明了返回值,也是无效的(让人不解的是 Remix 也不会因为这个报错),因为调用的时候只会返回交易哈希。
#热议区块链#
至此,第一部分 教学篇完结
后续计划 更新第二部分 进阶篇
NO.13 自建智能合约 Prepare
NO.14 编写智能合约 Compile
NO.15 部署智能合约 Deploy
NO.16 查看智能合约 Etherscan
NO.17 测试智能合约 Mocha
添加新手交流群:币种分析、每日早晚盘分析
添加助理微信,一对一亲自指导:YoYo8abc