进入Cloud IDE开发环境
可通过以下任一方式进入 Cloud IDE 合约开发环境:
在BaaS 平台BaaS 平台,单击蚂蚁区块链 > 合约链,单击目标链操作栏下的管理,单击左侧导航栏上的合约工程管理。在合约工程管理页,您可以通过创建合约工程或编辑已有的合约,进入 Cloud IDE 合约开发环境。
您也可以单击蚂蚁区块链 > 概览页面的合约体验链卡片上的调试合约,进入Cloud IDE开发环境。
Cloud IDE 合约工程管理
可以通过Cloud IDE对合约工程内的文件夹、文件进行 新建、删除、修改名字 操作。
在 Cloud IDE 的左侧导航栏中,第一个功能模块就是合约工程管理。在该模块,您可以对合约工程内的文件夹、文件进行新建、删除、重命名等操作。
添加文件夹。
添加文件,如果是 Solidity 合约文件,需要以
.sol
为扩展名。拖拽、移动文件或文件夹。
删除文件或文件夹。
您也可单击根目录的下载图标,将整个文件夹压缩下载至本地。
从合约工程管理页面进入Cloud IDE,默认实时保存文件。
当前Cloud IDE仅支持对Solidity合约进行编译,新增合约文件请以
.sol
结尾,否则编译失败。
Cloud IDE 合约开发流程
使用 Cloud IDE 开发合约的过程主要分为以下 5 步:
选择目标合约链
在Cloud IDE中,单击左上角的链名称进入环境配置,选择目标的合约链环境。
在环境配置中,您可以选择自己已有的链配置环境。例如,您已申请体验链,则可以选择已有的公共 合约链,该体验链会配备一个默认的测试账户,您可以直接用来部署、调用合约。如果此测试账户不能满足需求,您也可以使用通过 SDK 创建的账户,但是需要填入私钥(16 进制字符串),因为在部署、调用合约时IDE都需要账户的私钥对交易做签名处理。
关于如何获取 16 进制字符串形式的私钥,参见准备合约链的账户中的示例代码。
编译合约
开发合约过程中,可以随时对已完成的代码进行编译。
在 Cloud IDE 中,点击 编译,开始编译代码。
字节码即合约代码的编译结果,也是合约部署时使用的关键数据,通过 SDK 可以将合约字节码部署到目标的生产链上使用。
合约接口说明(ABI)应用程序二进制接口(Application Binary Interface,ABI)可以理解为合约的接口说明。当合约被编译后,其对应的 ABI 也会一起生成。
ABI 类似于程序中的接口说明文档,描述了属性和方法签名相关信息,包括字段名称、字段类型、方法名称、参数名称、参数类型、方法返回值类型等。
ABI 参数说明:
参数 | 说明 |
name | 函数名称 |
type | 方法类型,包括 function、constructor、fallback(缺省方法),默认为 function。 |
constant | 布尔值,如果为 true,则表示方法不会修改合约字段的状态变量。 |
payable | 布尔值,表示方法是否可以接收系统转账。 |
stateMutability | 状态类型,包括 pure(不读取区块链状态)、view(和 constant 类型一样,只能查看,不会修改合约字段)、nonpayable(和 payable 含义一样)、payable(和 payable 含义一样)。 |
inputs | 数组,描述参数的名称和类型。 |
name | 参数名称 |
type | 参数类型 |
outputs | 和 inputs 一样,如果没有返回值,缺省是一个空数组。 |
更多 ABI 相关信息,参见 Solidity ABI 官方介绍(英文)。
部署合约
合约编译通过后,单击左侧导航栏上的部署图标,即可查看编译后得到的字节码和ABI文件。在 Cloud IDE 中单击部署合约,将合约部署到目标体验链进行测试。
在部署定义了 constructor
方法的合约时,需要给定 constructor
方法的参数。Cloud IDE 支持合约方法参数类型的提示和一些基本数据类型的初始化赋值,您可根据自身实际情况初始化合约。
部署成功后,可以看到合约的所有方法列表。
调用合约
合约部署成功后,IDE 会列出合约中所有的 public 方法以及 public 类型的状态变量。点击目标方法右侧的 调用合约 进行调用。
其中,状态变量对应的调用可以直接返回当前状态变量的值。合约方法的调用可以返回如下几个字段:
参数 | 说明 |
input | 合约方法的输入数据,通常为合约方法的参数。 |
output | 合约方法的返回值,可能是多个值,根据返回值具体类型转码显示。 |
log | 如果在合约中使用了 Event 事件,并且事件被触发,则会在 log 中展示。 |
错误说明
在使用 Cloud IDE 过程中,可能遇到一些错误提示。在调用合约时,如果合约执行过程中出错,可能有多种原因,Cloud IDE 会将合约调用失败的错误码信息返回,便于您分析原因。Cloud IDE 使用的错误码信息与 SDK 使用的错误码保持一致,您可以查看 合约链错误码找到对应的错误码说明。
下面通过一个错误码示例来说明如何通过错误说明的提示分析合约内部错误的原因,您也可以通过添加 Event 事件的方式来调试合约。
错误码示例:
在上图的错误码示例中,系统调用了 Cloud IDE 示例合约的 voteForCandidate
方法,给定的候选人为Demi
,返回的错误码为10201
,其含义为:
错误码 | 错误码值 | 描述 |
VM_REVERT | 10201 | 触发 revert 指令导致的错误 |
根据错误码提示,可以判断调用合约失败是因为触发了 revert
语句,含义为某个 require()
方法判断失败。查看合约代码,即可发现 voteForCandidate
方法中的相关条件检查:
require(validCandidate(candidate));
由此,可推断出输入的候选人 Demi
不合法,并不在实际投票的候选人列表 candidateList
中。事实也确实如此,合约部署过程中,constructor
参数仅指定了 Nick
和 Rose
两个候选人参与竞选。
调试合约
目前,您可以通过添加 Event
事件触发日志的方式实现逻辑分支的判断和分析,进而对合约内部逻辑进行调试。
这里选择调用合约方法 validCandidate
。
// This function will help to check whether target candidate is in the candidateList.
function validCandidate(bytes32 candidate) view public returns (bool) {
for (uint i = 0; i < candidateList.length; i++) {
if (candidateList[i] == candidate) {
emit VALID(true);
return true;
}
}
emit VALID(false);
return false;
}
在 validCandidate
方法的实现中,不同的执行逻辑会根据情况触发不同的 Event
事件,通过查看调用结果日志信息,即可分析出合约的执行路径。在以上示例中,执行逻辑相对简单。在复杂的逻辑控制方法中,通过添加 Event
事件的方式可以很好的追踪合约执行路径,达到调试合约的目的。
合约单步调试
合约调试模块增加了一个虚拟机模拟器,可以模拟运行 Solidity 合约并给出执行结果。当前虚拟机模拟器与真实合约链有一些差异,主要差异在于虚拟机模拟器暂时不支持对 JSON/XML 的解析,将在后续功能升级中提供支持。
通常,对一个交易的 Debug 过程如下:
编译并部署合约到虚拟机模拟器中。
下图示例中,部署创建工程时默认添加的 demo 样例合约,部署时初始化了两个候选人“Demi”和“Rose”:
调用要调试的目标合约方法,产生执行的交易 hash。
下图示例中,调用默认 demo 样例的方法
voteForCandidate
:在底部调试详情信息栏,可单击调试按钮直接调试步骤 2 产生的目标交易。
可以单步单击图示中的箭头按钮,进行单步调试,同时查看运行时的局部变量、合约存储状态、堆栈、内存等关键信息进行深入调试。
合约单元测试
Cloud IDE 支持编写单元测试用例,所有的测试用例文件规定存放在 test
目录下,便于管理。
当前,IDE 支持使用 Solidity 语言编写单元测试。IDE 提供了便于编写测试用例的 Asset 库方法和测试账户。
为方便使用,在 IDE 单元测试模块中单击添加测试文件,可以快速生成一个单元测试用例,并且生成后的测试用例可以直接运行:
创建合约工程时,默认添加了一个 demo 合约,针对这个 demo 合约的单元测试用例如下:
pragma solidity ^0.4.20;
import "ide_tests.sol"; // the import is automatically injected by IDE.
import "../demo.sol"; // the path of target contract to test.
contract test_vote {
Voting voteToTest; // the target testing contract
function beforeAll () public {
bytes32[] params;
params.push(bytes32(1));
voteToTest = new Voting(params);
Assert.equal(voteToTest.totalVotes(), uint(0), "0 should be the total votes");
}
function checkTotalVotes () public {
Assert.equal(voteToTest.validCandidate(bytes32(1)), true, "should be a valid candidate");
voteToTest.voteForCandidate(bytes32(1));
Assert.equal(voteToTest.totalVotes(), uint(1), "1 should be the total votes");
}
}
运行此单元测试的结果为:
引入目标测试合约
单元测试的核心目标是测试一个目标合约,因此在单元测试中要引入需要测试的合约(
import "../demo.sol";
),然后创建并初始化此目标合约(voteToTest = new Voting(params);
),进而调用此目标合约的方法进行测试。Asset 库支持
其中
import "ide_tests.sol"
是 Cloud IDE 提供的 Asset 库,核心提供了不同合约数据类型的equal
、notEqual
、greaterThan
、lesserThan
、ok
等方法。如果代码中没有显式的引入ide_tests.sol
文件,Cloud IDE 也会自动注入此依赖库,方便您使用。在使用 Asset 的比较方法时,有以下注意事项:
类型 identity 的对比要使用
equalID
和notEqualID
方法。Asset 库中没有提供 uint8 ~ uint128 以及 int8 ~ int128 的对比方法,这些数据类型的比较都可以通过转换为 uint 和 int 进行,例如
Assert.equal(uint(2), uint(1), "error message defined by case");
。除此之外,Asset 库还提供一些单元测试执行前后的处理方法:
beforeAll
、beforeEach
、afterAll
、afterEach
,可以根据具体需求或场景使用。
TestsAccounts
测试账户在某些特殊场景下,需要指定合约的调用方(sender),比如测试角色权限控制,特定的角色才能调用某些合约方法。
在测试用例运行前,IDE 会默认随机创建 3 个测试账户以供使用,这 3 个账户的 ID 随机生成,并不固定。在编写测试用例方法时,可以通过写注释的方式指定账户,这样调用此用例方法时就会使用指定的账户来签名交易。例如以下使用方式:
/// sender: account-0 function checkSenderIsAccount0 () public { // the commits 'sender: account-0' says to use 'TestsAccounts.getAccount(0)' as sender. Assert.equalID(msg.sender, TestsAccounts.getAccount(0), "wrong sender in checkSenderIsAccount0"); }
其中
/// sender: account-0
注释非常关键,指定了使用 IDE 生成的 3 个账户中的第一个account-0
来调用方法checkSenderIsAccount0
进行测试。在代码中对应获取account-0
的方式是:TestsAccounts.getAccount(0)
。同样,如果需要使用其他两个账户,只需指定为
/// sender: account-1
或/// sender: account-2
即可。重要需要引入
import "ide_accounts.sol";
。