处理异常

在合约执行过程中,一旦出现异常,合约会立即停止执行并回滚其所造成的一切变更以确保世界状态不会受其影响,即本次合约调用所涉及的存储变更和 TransferBalance 函数造成的变化都不会生效。注意,如果出现合约异常之前已使用 Log 接口发出通知事件,那么这些信息将在回执(receipt)中体现。

异常分类

异常产生的原因有以下几类:

  • 通过调用 Revert()Require() 主动抛出异常,此时异常信息会进入到交易回执的output字段中。

  • 合约代码存在逻辑错误,导致mychainlib等第三方库中抛出异常。

  • 合约代码存在逻辑错误,导致虚拟机执行字节码过程中抛出异常。

  • 编译好合约后,不小心修改了字节码文件,导致合约是非法的字节码。

  • 调用合约时,指定的合约接口名称或合约接口参数不正确。

当合约调用执行出错时,您可以在交易回执的 output 字段找到详细的错误信息。常见的错误见下表。

错误码

错误信息

错误原因

10200

gas耗尽。

10201

METHOD_NOT_FOUND

找不到用户指定的合约方法。

10201

PARAMS_NOT_MATCH

用户指定的参数与合约方法定义不匹配。

10201

MYCHAINLIB_ERROR

mychainlib 内部异常,一般是错误的接口使用导致的。

10201

RAPIDXML_ERROR

rapidxml 库解析 XML 时出错。

10201

LIBCXX_ERROR

C++ 标准库内部异常,一般是使用了平台不支持的C++特性导致的。

10201

ABORT_CALLED

第三库或者用户自己调用了 abort()并且未提供任何错误提示。

10201

VM_MEMORY_OUT_OF_BOUNDS

读内存地址超出边界,检查合约代码是否有bug。

10201

VM_INTERGER_OVERFLOW

1. 浮点数到整数转换出现了溢出。

2.用有符号数的最小值除以-1导致溢出。例如:char最小值-128,-128/-1为128导致溢出。

10201

VM_DIVIDE_BY_ZERO

除0错误。

10201

VM_COVERT_NAN_TO_INT

NAN浮点无法强制转换Int。

10201

VM_INVALID_BYTE_CODE

非法的字节码,请检查编译器版本是否匹配,或检查字节码有没有被擅自修改。

10201

VM_UNINITIALIZED_TABLE_ELEMENT

检查合约有没有越界、非法指针等异常、合约内存使用超过可用内存等问题。

10201

VM_UNDEFINED_TABLE_INDEX

检查合约有没有越界、非法指针等异常、合约内存使用超过可用内存等问题。

10201

VM_OUT_OF_CALL_STACK

函数调用栈空间耗尽,可能函数调用深度太多,例如递归很深。

10201

VM_VALUE_STACK_EXHAUSTED

数据堆栈溢出,检查是不是递归太多了。

10201

VM_HOST_RESULT_TYPE_MISMATCH

系统函数返回值类型不匹配。

10201

VM_ARGUMENT_TYPE_MISMATCH

函数参数不匹配(参数个数,参数类型) 。

10201

VM_UNKNOWN_EXPORT

目标函数未定义

1. 检查编译器是否被擅自修改过或恶意修改过。

2. 检查字节码是否被擅自修改或恶意修改过。

10201

VM_EXPORT_KIND_MISMATCH

被导出的对象不支持该操作

1. 检查编译器是否被擅自修改过或恶意修改过。

2. 检查字节码是否被擅自修改或恶意修改过。

3. 不要用extern导出变量,函数。

10201

VM_BUFFER_OVERFLOW

合约中出现了缓存溢出的情况。可能的原因:

1. 擅自或恶意修改了编译器中的文件。

2. 生成的字节码被擅自修改。

3. 若确定没有以上情况,请给我们提交一个bug。

10201

INVALID_INPUT_DATA

合约无法解析交易传入的方法名和参数列表,请确定一下传入的参数个数、类型是否正确。或者是合约调用合约过程中,传入的参数个数、类型是否正确。

10500

内部异常,请联系管理员。

10622

合约在更新、部署或调用时发现合约字节码不是合法的wasm字节码,若在部署合约或更新合约出现,请检查使用的wasc文件是否正确。常见的情况是使用了高版本的mycdt编译合约字节码,然后尝试将该字节码部署在较低版本的mychain上。若mychain平台从低版本升级到高版本,注意必须发送一个交易激活升级(如何激活请联系管理员)。

10623

合约在部署或更新时无法识别合约格式,排查是不是正确使用了wasc文件。

合约互相调用过程中的异常处理

如果在合约相互调用过程中出现异常,处理规则如下:

  • A->B,B 执行过程中出现异常,那么 B 造成的一切世界状态变化都不会生效,A 不受影响。

  • A->B,如果在调用 B 之后 A 合约出现异常,A 合约造成的一切世界状态变化都不会生效。B 造成的一切世界状态变化都不会生效。

上面所提到的“造成的一切世界状态变化”指的是存储变更和 TransferBalance 函数造成的变化。而 Log(data,topics) 所产生的事件,无论合约是否出现异常,都会进入交易回执中。

说明

“A->B”表示 A 合约调用 B 合约,更多内容,参见 合约间调用

合约调用栈信息

合约异常终止时,虚拟机会在交易回执中新增一条主题为 “backtrace” 的 log,记录合约退出时的调用栈信息。

如果编译合约时保存了函数名(使用编译选项-dump-names),那么调用栈信息会包含每个被调用的函数名,如:

#0: revert_internal()
#1: Contract::revert()
#2: std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > mychain::execute_interface<Contract, void, Contract>(void (Contract::*)(), std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)
#3: apply

如果没有保存函数名,那么调用栈会包含每个被调用函数的编号,如:

#0: $func_55
#1: $func_60
#2: $func_58
#3: $func_56

假设用户部署合约时没有保存函数名,得到了由函数编号组成的调用栈信息,此时仍可以借助 MYCDT 把函数编号映射到原始函数名,方法如下:

使用my++重新编译原始的合约代码,并指定选项-dump-names,除了生成 wasc 字节码文件外,还会生成一个后缀名为 names 的文件。names 文件中按照函数编号顺序列出了所有函数名,比如第一行对应第 0 个函数,第二行对应第 1 个函数,以此类推。