合约调试帮助说明

naoye 2月前 159

第一步:选择交易

        合约调试时,首先需要选择调试哪笔交易。由于交易没有名称,我们添加了调用时间以便进行区分。


第二步:查看log/notify

        log/notify显示的是调用合约的结果。它的里面有四个比较重要的字段:vmstate:合约执行情况;gas_consumed:本次调用合约所花费gas;stack:合约返回;notify:合约抛出的通知。


      · vmstate

        首先需要观察vmstate字段的值。如果是FAULT,BREAK 那意味着你的合约异常奔溃了,没有完成整个调用流程。如果vmstate字段显示的是HALT, BREAK  那表示你的合约执行顺利完成。注意这里的执行完成仅仅代表没有抛出异常,不代表结果符合预期。

      · gas_consumed

        这里只需要检查是否超过10gas。超过10gas的话调用合约需要支付额外费用,否则会调用失败。如果超出了,可以查看Careinfo以找出哪些操作导致gas超出。

      · stack

        我们合约中一般都设有返回false和true两种预期结果,而stack字段显示的就是合约的返回状态。一般返回false会显示0,true会显示1。如果你的代码中没有写返回,或者返回其他内容,stack中会没有值或显示你所设置的内容。

      · notify

        在合约代码中可以定义notify来向链上抛出通知,那么这些notify就在notifications字段中呈现。这个类似于一般调试代码中的打印log。


第三步:调试AVM

        avm表示的是NEO虚拟机的执行步骤。我们将虚拟机的执行步骤与合约源码、堆栈数据进行映射,便可以看到代码在虚拟机中的执行顺序,以及内存中的数据是如何变化的。

        调试时,我们需要顺着虚拟机的顺序,逐个点击其AVM文件中的步骤,查看其每一步的执行所对应的源码是什么,数据有没有变化。为了方便起见,你可以在选中某一步后,按住↑或↓箭头,快速遍历。嗯……这就如同程序在执行一般。与一般调试程序不同的是,这里还不能自动打断点,只能手动断点——你只要停下来,任意位置都是断点。

        avm你可能会看不懂,但没关系,一般情况下你只要盯着源码就行,需要时看一眼堆栈里的数据。熟悉之后,你可能可以从avm中一眼就看出问题来。所以我们也提供了avm的解释说明。这在NEO源码里就有的,我们只是翻译整理了一下,如果你感兴趣的话可以研究一下。

        另外,我们还有个valuetool小工具,可以将堆栈中的数据转换为string和十进制的数值,需要你点击一下堆栈中的数字才能看到。enjoy it。


可选步骤:查看Careinfo

        在log/notify中你可以看到总的gas消耗,但并不能知道gas是在哪里被消耗掉了。在neo的智能合约体系中,每个普通指令都会消耗0.001gas,但一些关键操作会消耗大量gas。在Careinfo里会将你合约中大量消耗gas的操作显示出来。你可以根据这个调整你的代码逻辑以避免超出10gas的免费额度。       



附录1    系统使用费

智能合约中系统调用的手续费:

SysCall手续费 [Gas]
Runtime.CheckWitness0.2
Blockchain.GetHeader0.1
Blockchain.GetBlock0.2
Blockchain.GetTransaction0.1
Blockchain.GetAccount0.1
Blockchain.GetValidators0.2
Blockchain.GetAsset0.1
Blockchain.GetContract0.1
Transaction.GetReferences0.2
Account.SetVotes1
Validator.Register1000
Asset.Create(系统资产)5000
Asset.Renew(系统资产)5000
Contract.Create*100~1000
Contract.Migrate*100~1000
Storage.Get0.1
Storage.Put [per KB]1
Storage.Delete0.1
其它(每行OpCode)0.001

指令费用

Instruction手续费 [Gas]
OpCode.PUSH16 [or less]0
OpCode.NOP0
OpCode.APPCALL0.01
OpCode.TAILCALL0.01
OpCode.SHA10.01
OpCode.SHA2560.01
OpCode.HASH1600.02
OpCode.HASH2560.02
OpCode.CHECKSIG0.1
OpCode.CHECKMULTISIG(每个签名)0.1
其它(每行OpCode)0.001


附录2    AVM说明:


///将一个空的字节数组推入堆栈

PUSH0=0x00,

PUSHF=PUSH0,

 

///0x01-0x4B下一个操作码字节是要压入堆栈的数据

PUSHBYTES1=0x01,

PUSHBYTES75=0x4B,

 

///下一个字节包含要压入堆栈的字节数

PUSHDATA1=0x4C,

 

///接下来的两个字节包含要压入堆栈的字节数。

PUSHDATA2=0x4D,

 

///接下来的四个字节包含要压入堆栈的字节数。

 

PUSHDATA4=0x4E,

 

///数字-1被压入堆栈。

PUSHM1=0x4F,

 

///数字1被压入堆栈。

PUSH1=0x51,

PUSHT=PUSH1,

 

///数字2被压入堆栈。

PUSH2=0x52,

 

///将数字3推入堆栈。

PUSH3=0x53,

 

///将数字4推入堆栈。

PUSH4=0x54,

 

///数字5被压入堆栈。

PUSH5=0x55,

 

///将数字6推入堆栈。

PUSH6=0x56,

 

///将数字7推入堆栈。

PUSH7=0x57,

 

///数字8被压入堆栈。

PUSH8=0x58,

 

///数字9被压入堆栈。

PUSH9=0x59,

 

///将数字10推入堆栈。

PUSH10=0x5A,

 

///数字11被压入堆栈。

PUSH11=0x5B,

 

///将数字12推入堆栈。

PUSH12=0x5C,

 

///将数字13推入堆栈。.

PUSH13=0x5D,

 

///数字14被压入堆栈。

PUSH14=0x5E,

 

///数字15被压入堆栈。

PUSH15=0x5F,

 

///数字16被压入堆栈。

PUSH16=0x60,

 

流量控制


///什么也不做。

NOP=0x61,

 

///读取2字节值n,并执行跳转到相对位置n-3

JMP=0x62,

 

///布尔值b取自主堆栈并读取2字节值n,如果bTrue则跳转到相对位置n-3

JMPIF=0x63,

 

///布尔值b取自主堆栈并读取2字节值n,如果bFalse,则跳转到相对位置n-3

JMPIFNOT=0x64,

 

///当前上下文被复制到调用堆栈。读取2字节值n,并执行跳转到相对位置n-3

CALL=0x65,

 

///如果调用堆栈为空,则停止执行。

RET=0x66,

 

///读取一个scripthash并执行相应的合同。

APPCALL=0x67,

 

///读取字符串并执行相应的操作。

SYSCALL=0x68,

 

///读取一个scripthash并执行相应的合同。在调用堆栈上放置顶部项。

TAILCALL=0x69,

 

 

堆栈

 

///alt堆栈顶部复制项目并将其放在主堆栈的顶部。

DUPFROMALTSTACK=0x6A,

 

///将输入放在alt堆栈的顶部。从主堆栈中删除它。

TOALTSTACK=0x6B,

 

///将输入放在主堆栈的顶部。从alt堆栈中删除它。

FROMALTSTACK=0x6C,

 

///删除主堆栈中的项目n

XDROP=0x6D,

 

///与主堆栈交换的项目n与顶部堆栈项目交换。

XSWAP=0x72,

 

///复制主堆栈顶部的项目并将其插入主堆栈中的位置n

XTUCK=0x73,

 

///将堆栈项的数量放入堆栈。

DEPTH=0x74,

 

///删除顶部堆栈项。

DROP=0x75,

 

///复制顶部堆栈项。

DUP=0x76,

 

///删除第二个到顶部的堆栈项。

NIP=0x77,

 

///将第二个到顶部的堆栈项复制到顶部。

OVER=0x78,

 

///堆栈中的项目n被复制到顶部。

PICK=0x79,

 

///堆栈中的项目n移动到顶部。

ROLL=0x7A,

 

///堆栈顶部的三个项目向左旋转。

ROT=0x7B,

 

///堆栈中的前两项是交换的。

SWAP=0x7C,

 

///堆栈顶部的项目被复制并插入到第二个到顶部的项目之前。

TUCK=0x7D,

 

 

拼接


///连接两个字符串。

CAT=0x7E,

 

///返回字符串的一部分。

SUBSTR=0x7F,

 

///只保留字符串中指定点的左侧字符。

LEFT=0x80,

 

///只保留字符串中指定点的字符。

RIGHT=0x81,

 

///返回输入字符串的长度。

SIZE=0x82,

 

 

按位逻辑

 

///翻转输入中的所有位。

INVERT=0x83,

 

///输入的bit进行与运算

AND=0x84,

 

///输入的bit进行或运算

OR=0x85,

 

///输入的bit进行异或运算

XOR=0x86,

 

///如果输入完全相等则返回1,否则返回0

EQUAL=0x87,

//OP_EQUALVERIFY=0x88,//OP_EQUAL相同,但之后运行OP_VERIFY

//OP_RESERVED1=0x89,//除非在未执行的OP_IF分支中发生,否则事务无效

//OP_RESERVED2=0x8A,//除非在未执行的OP_IF分支中发生,否则事务无效


 

 算术

注意:算术输入仅限于带符号的32位整数,它的输出可能会溢出

 

///1添加到输入中。

INC=0x8B,

 

///从输入中减去///1

DEC=0x8C,

 

///将顶部堆栈项的符号放在主堆栈的顶部。如果值为负,则输入-1;如果是积极的,放1;如果value为零,则输入0

SIGN=0x8D,

 

///翻转输入的符号。

NEGATE=0x8F,

 

///输入结果为正。

ABS=0x90,

 

///如果输入为01,则将其翻转。否则输出将为0

NOT=0x91,

 

///如果输入为0,则返回0.否则返回1

NZ=0x92

 

///a被添加到b。

ADD=0x93

 

///a中减去b

SUB=0x94

 

///a乘以b

MUL=0x95

 

///a除以b

DIV=0x96

 

///除以b之后得到的余数。

MOD=0x97

 

///左移b位,保留符号。

SHL=0x98

 

///右移b位,保留符号。

SHR=0x99

 

///如果ab都不为0,则输出为1.否则为0

BOOLAND=0x9A

 

///如果ab不为0,则输出为1.否则为0

BOOLOR=0x9B

 

///如果数字相等则返回1,否则返回0

NUMEQUAL=0x9C

 

///如果数字不相等则返回1,否则返回0

NUMNOTEQUAL=0x9E

 

///如果a小于b则返回1,否则返回0

LT=0x9F

 

///如果a大于b则返回1,否则返回0

GT=0xA0

 

///如果a小于或等于b则返回1,否则返回0

LTE=0xA1

 

///如果a大于或等于b则返回1,否则返回0

GTE=0xA2

 

///返回ab中较小的一个。

MIN=0xA3

 

///返回ab中较大的一个。

MAX=0xA4

 

///如果x在指定范围内(包含左侧),则返回1,否则返回0

WITHIN=0xA5

 

 

加密

RIPEMD160=0xA6//使用RIPEMD-160对输入进行哈希处理。

 

///使用SHA-1对输入进行哈希处理。

SHA1=0xA7

 

///使用SHA-256对输入进行哈希处理。

SHA256=0xA8

 

///使用Hash160对输入进行哈希处理:首先使用SHA-256,然后使用RIPEMD-160

HASH160=0xA9

 

///使用Hash256对输入进行哈希处理:使用SHA-256进行两次哈希处理。

HASH256=0xAA

 

///公钥和签名取自主堆栈。验证事务是否由给定的publickey签名,并且布尔输出放在主栈的顶部。

CHECKSIG=0xAC

 

///公钥,签名和消息取自主堆栈。验证给定的消息是否由给定的publickey签名,并且布尔输出放在主堆栈的顶部。

VERIFY=0xAD

 

///一组n个公钥(一个数组或值n后跟npubkeys)根据一组m个签名(数组或值m后跟m个签名)进行验证。将事务验证为multisig,并将布尔输出放在主堆栈的顶部。

CHECKMULTISIG=0xAE

 

 

数组

 

///从主堆栈的顶部删除一个数组。它的大小放在主堆栈的顶部。

ARRAYSIZE=0xC0

 

///n取自主堆栈的顶部。主堆栈中的下n个项目被移除,放入n大小的数组中,并且此数组放在主堆栈的顶部。

PACK=0xC1

 

///从主堆栈的顶部删除一个数组。它的元素放在主堆栈的顶部(按相反顺序),数组大小也放在主堆栈上。

UNPACK=0xC2

 

///输入索引n(或键)和数组(或映射)取自主堆栈。元素数组[n](或map[n])放在主堆栈的顶部。

PICKITEM=0xC3

 

///v,索引n(或键)和数组(或映射)取自主堆栈。归属数组[n]=v(或map[n]=v)被执行。

SETITEM=0xC4

 

///用作引用类型en:值n取自主堆栈的顶部。大小为n的零填充数组类型放在主堆栈的顶部。

NEWARRAY=0xC5

 

///用作值类型en:值n取自主堆栈的顶部。大小为n的零填充结构类型放在主堆栈的顶部。

NEWSTRUCT=0xC6

 

///创建一个Map并将其放在主堆栈的顶部。

NEWMAP=0xC7

 

///删除主堆栈顶部的项目并将其附加到主堆栈顶部的第二个项目。

APPEND=0xC8

 

///从主堆栈的顶部删除一个数组,并反转其元素。

REVERSE=0xC9

 

///从主堆栈的顶部删除输入索引n(或键)和数组(或映射)。元素数组[n](或map[n])被删除。

REMOVE=0xCA

 

///从主堆栈的顶部删除输入索引n(或键)和数组(或映射)。如果array[n](或map[n])存在,则在主堆栈顶部显示True,否则返回False

HASKEY=0xCB

 

///从主堆栈的顶部获取地图。此映射的键放在主堆栈的顶部。

KEYS=0xCC

 

///从主堆栈的顶部获取地图。此映射的值放在主堆栈的顶部。

VALUES=0xCD

 

 

//堆栈隔离

CALL_I=0xE0

CALL_E=0xE1

CALL_ED=0xE2

CALL_ET=0xE3

CALL_EDT=0xE4

 

 

例外

 

///通过设置VMState.FAULT来暂停执行vm

THROW=0xF0

 

///删除顶部堆栈项n,并仅在nFalse时通过设置VMState.FAULT来停止执行vm

THROWIFNOT=0xF1

 


最后于 2月前 被naoye编辑 ,原因:
最新回复 (0)
全部楼主
返回