退出码(Exit codes)
TON Blockchain上的每笔交易都包含多个阶段。 退出码 是一个 位符号整数 这表明交易的 计算(compute) 或 动作(action) 阶段是成功的。 如果没有,则保留例外的代码。 每个退出码代表自己的异常或交易结果。
退出码 和 表示正常(成功)执行 计算阶段。 退出 (或 result) 代码 表示了[动作阶段] (#action)的正常执行(成功)。 任何其他退出代码都表示发生了某种异常,交易以某种方式没有成功,即交易被退回或入站(inbound)报文被弹回。
TON 区块链保留从 到 的退出码值,而 Tact 使用从 到 的退出码。 注意, Tact使用的退出码表示使用Tact生成FunC代码时可能发生合约错误。 因此被扔进交易的计算阶段,而不是在编译过程中。
从 到 的范围内,开发人员可自由定义退出码。
退出码列表
下表列出了每个退出码的来源(可能出现的位置)和简短说明。 该表没有列出 require()
的退出码,因为它是根据具体的 error
消息 String 生成的。
退出码 | 来源 | 说明 |
---|---|---|
计算和动作阶段 | 标准成功执行退出码 | |
计算阶段 | 替代成功的执行退出码。 保留,但不会出现。 | |
计算阶段 | 堆栈下溢。 | |
计算阶段 | 堆栈溢出。 | |
计算阶段 | 整数溢出。 | |
计算阶段 | 范围检查错误 — 某些整数超出预期范围。 | |
计算阶段 | 无效的 TVM opcode | |
计算阶段 | 类型检查错误。 | |
计算阶段 | Cell 溢出。 | |
计算阶段 | cell下溢。 | |
计算阶段 | 字典错误。 | |
计算阶段 | TVM 文档被描述为“未知错误,可能会被用户程序抛出”。 | |
计算阶段 | 致命错误。 由于被认为是不可能的情况而由 TVM 抛出。 | |
计算阶段 | gas 耗尽错误。 | |
计算阶段 | 与 相同。 负数,因此无法伪造。 | |
计算阶段 | 虚拟机虚拟化错误。 保留,但从未抛出。 | |
行动阶段 | 操作列表(Action list)无效。 | |
行动阶段 | 操作列表太长。 | |
行动阶段 | 行动无效或不支持。如果无法执行当前操作,则在行动阶段设置 | |
行动阶段 | 发送消息中无效的源地址。 | |
行动阶段 | 发送消息中无效的目标地址。 | |
动作阶段 | 没有足够的Toncoin。 | |
动作阶段 | 额外代币不足。 | |
行动阶段 | 发送消息在重写后不适合在cell中。 | |
行动阶段 | 无法处理一条消息 — 资金不足,信息过大,或者它的 Merkle 深度过大。 | |
行动阶段 | 在库更改操作期间,库引用是无效的。 | |
行动阶段 | 库更改动作错误。 | |
行动阶段 | 超出库中cell的最大数目或默克尔树的最大深度。 | |
行动阶段 | 账户状态大小超过限制。 | |
Tact 编译器 (计算阶段) | 空引用异常。 | |
Tact 编译器 (计算阶段) | 无效的序列化前缀。 | |
Tact 编译器 (计算阶段) | 无效的收到消息 — 没有接收消息的验证码。 | |
Tact 编译器 (计算阶段) | 限制错误。 保留,但从未抛出。 | |
Tact 编译器 (计算阶段) | 拒绝访问 - 所有者以外的其他人向合约发送了信息 | |
Tact 编译器 (计算阶段) | 合约已中止。 保留,但从未抛出。 | |
Tact 编译器 (计算阶段) | 无效参数。 | |
Tact 编译器 (计算阶段) | 未找到合约代码 - 字典调用的假标记 | |
Tact 编译器 (计算阶段) | 无效地址。 | |
Tact 编译器 (计算阶段) | 此合约未启用主链支持 |
在Blueprint项目中退出码
在Blueprint测试中,计算阶段的退出码在expect()
匹配器的 toHaveTransaction()
方法的对象参数的 exitCode
字段中指定。 相同的 toHaveTransaction()
方法的 result 代码(#action) 的退出码,称为actionResultCode
。
此外, 我们可以查看发送消息到合约的结果,并发现每个交易的阶段及其值。 包括计算阶段(#compute)(或行动阶段)的退出(或结果)代码。
请注意,为了做到这一点,您必须先进行几种类型的检查才能做到:
it('tests something, you name it', async () => { // Send a specific message to our contract and store the results const res = await your_contract_name.send(…);
// Now, we have an access to array of executed transactions, // with the second one (index 1) being the one that we look for const tx = res.transactions[1]!;
// To do something useful with it, let's ensure that it's type is 'generic' // and that the compute phase in it wasn't skipped if (tx.description.type === "generic" && tx.description.computePhase.type === "vm") { // Finally, we're able to freely peek into the transaction for general details, // such as printing out the exit code of the compute phase if we so desire console.log(tx.description.computePhase.exitCode); }
// ...});
如果没有足够的 TON 来处理计算阶段,则会抛出此错误。
0: Normal termination
该退出码表示事务的计算阶段已成功完成。
计算阶段
如果计算阶段失败(产生的退出代码不是 或 ),事务将跳过操作阶段,进入反弹阶段。 在其中,回弹消息是为入站消息发起的交易形成的。
1: Alternative termination
这是成功执行计算阶段的替代退出码。 保留,但从未发生。
2: Stack underflow
如果某些操作消耗的元素多于堆栈上的元素,则退出码 的错误就会抛出:“堆栈溢出”。
asm fun drop() { DROP }
contract Loot { receive("I solemnly swear that I'm up to no good") { try { // Removes 100 elements from the stack, causing an underflow repeat (100) { drop() } } catch (exitCode) { // exitCode is 2 } }}
3: Stack overflow
如果有太多元素被复制到关闭继续或存储在堆栈上, 退出码 出错:“堆栈溢出”。 很少出现,除非你深陷 Fift 和 TVM 组装 战壕:
// Remember kids, don't try to overflow the stack at home!asm fun stackOverflow() { <{ }>CONT // c 0 SETNUMARGS // c' 2 PUSHINT // c' 2 SWAP // 2 c' 1 -1 SETCONTARGS // <- this blows up}
contract ItsSoOver { receive("I solemnly swear that I'm up to no good") { try { stackOverflow(); } catch (exitCode) { // exitCode is 3 } }}
4: Integer overflow
整数溢出。整数不适合 或发生了零除法
let x = -pow(2, 255) - pow(2, 255); // -2^{256}
try { -x; // integer overflow by negation // since the max positive value is 2^{256} - 1} catch (exitCode) { // exitCode is 4}
try { x / 0; // division by zero!} catch (exitCode) { // exitCode is 4}
try { x * x * x; // integer overflow!} catch (exitCode) { // exitCode is 4}
// There can also be an integer overflow when doing:// addition (+),// subtraction (-),// division (/) by a negative number or modulo (%) by zero
5: Integer out of range
范围检查错误 — 某些整数超出预期范围。 I.e. 任何试图存储意外数量的数据或指定一个超出范围的值会造成退出码 的错误:“整数超出范围”。
指定非边界值的示例:
try { // Repeat only operates on inclusive range from 1 to 2^{31} - 1 // and any valid integer value greater than that causes an error with exit code 5 repeat (pow(2, 55)) { dump("smash. logs. I. must."); }} catch (exitCode) { // exitCode is 5}
try { // Builder.storeUint() function can only use up to 256 bits, so 512 is too much: let s: Slice = beginCell().storeUint(-1, 512).asSlice();} catch (exitCode) { // exitCode is 5}
6: Invalid opcode
如果您指定了在当前 TVM 版本中未定义的指令,退出码 将会丢失一个错误:“无效的opcode”。
// No such thingasm fun invalidOpcode() { x{D7FF} @addop }
contract OpOp { receive("I solemnly swear that I'm up to no good") { try { invalidOpcode(); } catch (exitCode) { // exitCode is 6 } }}
7: Type check error
如果原始参数的值类型不正确,或在计算阶段中的类型中存在任何其他不匹配的类型, 退出码 出错:“类型检查错误”。
// The actual returned value type doesn't match the declared oneasm fun typeCheckError(): map<Int, Int> { 42 PUSHINT }
contract VibeCheck { receive("I solemnly swear that I'm up to no good") { try { // The 0th index doesn't exist typeCheckError().get(0)!!; } catch (exitCode) { // exitCode is 7 } }}
8: Cell overflow
来自书的Cells, Builders and Slices page
要构建一个 cellent
, 使用 Builder
如果尝试存储的数据超过 位,或对其他cell的引用超过 ,则会出现退出码为 的错误:“Cell overflow
。
这个错误可以由[手动construction](/zh-cn/book/cells#cnp-manual y) 通过relevant .loadSomething()
方法 或使用结构和消息及其方便方法 触发。
// Too much bitstry { let data = beginCell() .storeInt(0, 250) .storeInt(0, 250) .storeInt(0, 250) .storeInt(0, 250) .storeInt(0, 24) // 1024 bits! .endCell();} catch (exitCode) { // exitCode is 8}
// Too much refstry { let data = beginCell() .storeRef(emptyCell()) .storeRef(emptyCell()) .storeRef(emptyCell()) .storeRef(emptyCell()) .storeRef(emptyCell()) // 5 refs! .endCell();} catch (exitCode) { // exitCode is 8}
9: Cell underflow
来自书的Cells, Builders and Slices page
若要解析cell
,请使用 slice
如果你试图加载比slice
更多的数据或引用,则退出码 的错误就会抛出:“cell下溢”。
此错误的最常见原因是cell的预期内存布局和实际内存布局不匹配, 因此建议使用结构和消息进行解析 ,而不是手动解析 通过 relevant . oadSomething()
方法。
// Too few bitstry { emptySlice().loadInt(1); // 0 bits!} catch (exitCode) { // exitCode is 9}
// Too few refstry { emptySlice().loadRef(); // 0 refs!} catch (exitCode) { // exitCode is 9}
10: Dictionary error
在 Tact 中,map<K, V>
类型是对 FunC 的 “hash” map 字典 和 TL-B 及 TVM 的底层 HashmapE
类型 的一种抽象。
如果不正确地操纵字典,如对其内存布局的错误假设, 退出码 出错:`字典错误’。 请注意,除非您自己使用 Fift 和 TVM assembly ,否则您无法获得此错误:
/// Pre-computed Int to Int dictionary with two entries — 0: 0 and 1: 1const cellWithDictIntInt: Cell = cell("te6cckEBBAEAUAABAcABAgPQCAIDAEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMLMbT1U=");
/// Tries to preload a dictionary from a Slice as a map<Int, Cell>asm fun toMapIntCell(x: Slice): map<Int, Cell> { PLDDICT }
contract DictPic { receive("I solemnly swear that I'm up to no good") { try { // The Int to Int dictionary is being misinterpreted as a map<Int, Cell> let m: map<Int, Cell> = toMapIntCell(cellWithDictIntInt.beginParse());
// And the error happens only when we touch it m.get(0)!!; } catch (exitCode) { // exitCode is 10 } }}
11: “Unknown” error
在TVM文档中被描述为 “未知错误,可能由用户程序抛出”,但最常用于队列消息发送问题或get-methods问题。
try { // Unlike nativeSendMessage which uses SENDRAWMSG, this one uses SENDMSG, // and therefore fails in Compute phase when the message is ill-formed nativeSendMessageReturnForwardFee(emptyCell(), 0);} catch (exitCode) { // exitCode is 11}
12: Fatal error
致命错误。 由于被认为是不可能的情况而由 TVM 抛出。
13: Out of gas error
如果没有足够的 gas 在计算阶段中结束计算, 则退出码 的错误将会抛出: ` gas 错误’.
但是这个代码并不会立即按原样显示——而是应用了按位非运算,从而将值从变为。 只有那时才显示代码。
这样做是为了防止在用户合约中人为地生成结果代码 (),因为所有可以抛出退出码的函数只能指定从 到 (含)范围内的整数。
try { repeat (pow(2, 31) - 1) {}} catch (exitCode) { // exitCode is -14}
-14: Out of gas error
见退出码13。
14: Virtualization error
虚拟化错误,与 prunned branch cells 有关。 保留,但从未抛出。
行动阶段
操作阶段 是在成功执行 计算阶段 后处理的。 它试图在计算阶段执行由 TVM 存储在行动列表中的动作。
有些操作可能在处理过程中失败。 在这种情况下,这些行动可能被跳过或整个交易可能会根据行动方式恢复。 表明行动阶段 结果状态的代码被称为 结果代码(result code) 。 因为它也是一个 位签名的整数,它基本上与计算阶段的 _export 代码](#compute)具有相同的用途, 通常将结果代码称为退出码。
32: Action list is invalid
如果操作列表包含外来的cell,则操作项cell没有引用,或者某些操作项cell无法解析, 退出码 出错:“行动列表无效”。
33: Action list is too long
如果排队等待执行的操作超过 ,操作阶段 将抛出错误,退出码为 :“操作列表太长”。
// For example, let's attempt to queue reservation of specific amount of nanoToncoins// This won't fail in compute phase, but will result in exit code 33 in Action phaserepeat (256) { nativeReserve(ton("0.001"), ReserveAtMost);}
34: Invalid or unsupported action
此刻只有四个支持的动作:更改合约代码,发送一条消息, 保留特定数量的 nanoToncoins 并更改library cell。 如果指定动作有任何问题 (无效消息,不支持的动作等),则退出码 的错误将被抛出:`无效或不支持的动作’。
// For example, let's try to send an ill-formed message:nativeSendMessage(emptyCell(), 0); // won't fail in compute phase, // but will result in exit code 34 in Action phase
35: Invalid source address in outbound message
如果出站消息中的源地址不等于addr_none
,或是引发此消息的合约地址, 退出码 出错:`出站消息中无效的源地址’。
36: Invalid destination address in outbound message
如果出站消息中的目标地址无效,例如: 它不符合相关的 TL-B 模式,其中包含未知的工作链ID或它对给定的工作链有无效的长度, 退出码 出现错误: “发送消息中无效的目标地址”。
37: Not enough Toncoin
如果带有基本模式 64设置的入站消息的所有资金已被消耗,并且没有足够的资金支付失败操作的费用,或者提供的值的 TL-B 布局(CurrencyCollection
)无效,或者没有足够的资金支付转发费用,或在扣除费用后资金不足,则会抛出错误,退出码为 :Not enough Toncoin
。
38: Not enough extra currencies
除了本地货币Toncoin外,TON区块链支持最多 种额外货币。 它们与创建新的Jetton 不同,因为额外的货币是原生支持的——可以在发送给另一个合约的内部消息中,除了 Toncoin 金额之外,额外指定一个额外货币金额的 HashmapE
。 与 Jettons 不同,额外货币只能存储和转移,没有任何其他功能。
目前,TON 区块链上没有额外的货币,但当没有足够的额外货币来发送指定数量的货币时,退出代码 已被保留:没有足够的额外货币”。
39: Outbound message doesn’t fit into a cell
在处理消息时,TON 区块链会尝试根据相关的 TL-B 模式对其进行打包,如果无法打包,则会抛出退出码为 的错误:Outbound message doesn't fit into a cell
。
40: Cannot process a message
如果没有足够的资金处理消息中的所有cell, 消息过大或它的 Merkle 深度过大, 退出码 出错:`无法处理消息’。
41: Library reference is null
如果在库更改操作过程中需要库引用,但该引用为空,则会出现退出代码为 的错误:“库引用为空”。
42: Library change action error
如果尝试修改库时出现错误,退出码 将会丢失: `Library 更改操作错误’。
43: Library limits exceeded
如果超出库中cell的最大数目或超过Merkle树的最大深度, 退出码 出错:库限制已超过
。
50: Account state size exceeded limits
如果账户状态(本质上是合约存储)在动作阶段结束时超过了 TON Blockchain 配置参数 43中规定的任意限制,则会抛出退出码为 的错误:Account state size exceeded limits
。
如果配置不存在,默认值为:
max_msg_bits
等于 — 消息的最大大小(位)。max_msg_cells
等于2^13$ — 最高数量的 cells 一个消息可以占用。max_library_cells
等于 - 可用作库参考cells的cells的最大数量。max_vm_data_depth
等于 — 最大 cells 消息和帐户状态深度。ext_msg_limits.max_size
等于 - 外部信息的最大位数。ext_msg_limits.max_depth
等于2 ^9$ — 最大外部消息 depth.max_acc_state_cells
等于2 ^16$ — 最高数量的 cells 账户状态可以占用。max_acc_state_bits
等于 — 帐户状态的最大大小(位)。max_acc_public_libraries
等于 ,即账户状态在主链上可使用的库引用单元的最大数量。deleger_out_queue_size_limit
等于2 ^8$ — — 最大数量的出站消息队列(关于验证器和校验器)。
Tact 编译器
Tact 使用从 到 的退出码。 请注意,Tact 使用的退出代码表示在使用 Tact 生成的 FunC 代码时可能出现的合约错误,因此在事务的计算阶段而不是在编译期间抛出。
128: Null reference exception
如果有一个非空断言,例如 !!
操作符,而检查值是 null
,则会抛出一个退出码为 的错误:“空引用异常”。
let gotcha: String? = null;
try { // Asserting that the value isn't null, which isn't the case! dump(gotcha!!);} catch (exitCode) { exitCode; // 128}
129: Invalid serialization prefix
保留,但由于有许多事先检查,除非在部署前劫持合约代码并更改合约中预期接收的 信息 的操作码,否则不会抛出。
130: Invalid incoming message
如果收到的内部或外部信息未被合约处理,则会出现退出码为 的错误:“Invalid incoming message”。 它通常发生在合约没有特定消息的接收器及其opcode 前缀(32位整数头)。
考虑下列合约:
import "@stdlib/deploy";
contract Dummy with Deployable {}
如果尝试发送任何消息,除了 @stdlib/deploy
提供的 部署
,合约将没有接收器,因此会抛出错误,退出码为 。
131: Constraints error
限制错误。 保留,但从未抛出。
132: Access denied
如果使用@stdlib/ownable
库中的Ownable
trait,由它提供的辅助函数 requireOwner()
会在入站消息的发件人与指定的所有者不匹配时抛出一个退出码为 的错误:Access denied
。
import "@stdlib/ownable";
contract Hal9k with Ownable { owner: Address;
init(owner: Address) { self.owner = owner; // set the owner address upon deployment }
receive("I'm sorry Dave, I'm afraid I can't do that.") { // Checks that the message sender's address equals to the owner address, // and if not — throws an error with exit code 132. self.requireOwner();
// ... you do you ... }}
133: Contract stopped
合约已停止 - 已向停止的合约发送信息 保留,但从未抛出。
134: Invalid argument
如果参数值无效或出乎意料,则会出现退出码为 的错误:“无效参数”。
以下是Tact 中的一些函数,这些函数可能导致此退出码出现错误:
-
Int.toFloatString(digits)
:如果digits
不在区间:digits
。 -
String.fromBase64()
和Slice.fromBase64()
:如果给定的String
或Slice
包含非 Base64 字符。
try { // 0 is code of NUL in ASCII and it is not valid Base64 let code: Slice = beginCell().storeUint(0, 8).asSlice().fromBase64();} catch (exitCode) { // exitCode is 134}
135: Code of a contract was not found
如果合约代码与 TypeScript 封装程序中保存的代码不匹配,则会出现退出码为 的错误:“未找到合约代码”。
136: Invalid address
类型 Address
的值在如下情况下有效:
- 它占用 位: 位用于链 ID 前缀, 位用于地址本身。
- 它属于两者之一:basechain(ID ) 或masterchain (ID ),后者需要启用 masterchain support。
如果 地址
无效,则会出现退出码为 的错误:无效地址
。
// Only basechain (ID 0) or masterchain (ID -1) are supported by Tactlet unsupportedChainID = 1;
try { // Zero address in unsupported workchain dump(newAddress(unsupportedChainID, 0));} catch (exitCode) { // exitCode is 136}
137: Masterchain support is not enabled for this contract
在未启用主链支持的情况下,任何指向主链(ID )或以其他方式与之交互的尝试都会产生异常,退出码为 :“此合约未启用主链支持”。
let masterchainID = -1;
try { // Zero address in masterchain without the config option set dump(newAddress(masterchainID, 0));} catch (exitCode) { // exitCode is 137}