跳转到内容

退出码(Exit codes)

TON Blockchain上的每笔交易都包含多个阶段退出码 是一个 3232位符号整数 这表明交易的 计算(compute)动作(action) 阶段是成功的。 如果没有,则保留例外的代码。 每个退出码代表自己的异常或交易结果。

退出码 0011 表示正常(成功)执行 计算阶段。 退出 (或 result) 代码 00 表示了[动作阶段] (#action)的正常执行(成功)。 任何其他退出代码都表示发生了某种异常,交易以某种方式没有成功,即交易被退回或入站(inbound)报文被弹回。

TON 区块链保留从 00127127的退出码值,而 Tact 使用从 128128255255的退出码。 注意, Tact使用的退出码表示使用Tact生成FunC代码时可能发生合约错误。 因此被扔进交易的计算阶段,而不是在编译过程中。

2562566553565535 的范围内,开发人员可自由定义退出码。

退出码列表

下表列出了每个退出码的来源(可能出现的位置)和简短说明。 该表没有列出 require() 的退出码,因为它是根据具体的 error 消息 String 生成的。

退出码来源说明
00计算动作阶段标准成功执行退出码
11计算阶段替代成功的执行退出码。 保留,但不会出现。
22计算阶段堆栈下溢。
33计算阶段堆栈溢出。
44计算阶段整数溢出。
55计算阶段范围检查错误 — 某些整数超出预期范围。
66计算阶段无效的 TVM opcode
77计算阶段类型检查错误。
88计算阶段Cell 溢出。
99计算阶段cell下溢。
1010计算阶段字典错误。
1111计算阶段TVM 文档被描述为“未知错误,可能会被用户程序抛出”。
1212计算阶段致命错误。 由于被认为是不可能的情况而由 TVM 抛出。
1313计算阶段gas 耗尽错误。
14-14计算阶段1313 相同。 负数,因此无法伪造
1414计算阶段虚拟机虚拟化错误。 保留,但从未抛出。
3232行动阶段操作列表(Action list)无效。
3333行动阶段操作列表太长。
3434行动阶段行动无效或不支持。如果无法执行当前操作,则在行动阶段设置
3535行动阶段发送消息中无效的源地址。
3636行动阶段发送消息中无效的目标地址。
3737动作阶段没有足够的Toncoin。
3838动作阶段额外代币不足。
3939行动阶段发送消息在重写后不适合在cell中。
4040行动阶段无法处理一条消息 — 资金不足,信息过大,或者它的 Merkle 深度过大。
4141行动阶段在库更改操作期间,库引用是无效的。
4242行动阶段库更改动作错误。
4343行动阶段超出库中cell的最大数目或默克尔树的最大深度。
5050行动阶段账户状态大小超过限制。
128128Tact 编译器 (计算阶段)空引用异常。
129129Tact 编译器 (计算阶段)无效的序列化前缀。
130130Tact 编译器 (计算阶段)无效的收到消息 — 没有接收消息的验证码。
131131Tact 编译器 (计算阶段)限制错误。 保留,但从未抛出。
132132Tact 编译器 (计算阶段)拒绝访问 - 所有者以外的其他人向合约发送了信息
133133Tact 编译器 (计算阶段)合约已中止。 保留,但从未抛出。
134134Tact 编译器 (计算阶段)无效参数。
135135Tact 编译器 (计算阶段)未找到合约代码 - 字典调用的假标记
136136Tact 编译器 (计算阶段)无效地址。
137137Tact 编译器 (计算阶段)此合约未启用主链支持

在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

该退出码表示事务的计算阶段已成功完成。

计算阶段

TVM 初始化和所有计算都发生在 计算阶段 中。

如果计算阶段失败(产生的退出代码不是 0011 ),事务将跳过操作阶段,进入反弹阶段。 在其中,回弹消息是为入站消息发起的交易形成的。

1: Alternative termination

这是成功执行计算阶段的替代退出码。 保留,但从未发生。

2: Stack underflow

如果某些操作消耗的元素多于堆栈上的元素,则退出码 22 的错误就会抛出:“堆栈溢出”。

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

如果有太多元素被复制到关闭继续或存储在堆栈上, 退出码 33 出错:“堆栈溢出”。 很少出现,除非你深陷 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

整数溢出。整数不适合 2256x<2256-2^{256} \leq x < 2^{256} 或发生了零除法

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. 任何试图存储意外数量的数据或指定一个超出范围的值会造成退出码 55的错误:“整数超出范围”。

指定非边界值的示例:

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 版本中未定义的指令,退出码 66 将会丢失一个错误:“无效的opcode”。

// No such thing
asm 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

如果原始参数的值类型不正确,或在计算阶段中的类型中存在任何其他不匹配的类型, 退出码 77 出错:“类型检查错误”。

// The actual returned value type doesn't match the declared one
asm 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

cell是一种基元和数据结构,它通常由最多 10231023 个连续排列的位和最多 44 个指向其他cell的引用(refs)组成。

要构建一个 cellent, 使用 Builder 如果尝试存储的数据超过 10231023 位,或对其他cell的引用超过 44 ,则会出现退出码为 88 的错误:“Cell overflow

这个错误可以由[手动construction](/zh-cn/book/cells#cnp-manual y) 通过relevant .loadSomething()方法使用结构和消息及其方便方法 触发。

// Too much bits
try {
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 refs
try {
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是一种基元和数据结构,它通常由最多 10231023 个连续排列的位和最多 44 个指向其他cell的引用(refs)组成。

若要解析cell,请使用 slice 如果你试图加载比slice更多的数据或引用,则退出码 99 的错误就会抛出:“cell下溢”。

此错误的最常见原因是cell的预期内存布局和实际内存布局不匹配, 因此建议使用结构和消息进行解析 ,而不是手动解析 通过 relevant . oadSomething() 方法

// Too few bits
try {
emptySlice().loadInt(1); // 0 bits!
} catch (exitCode) {
// exitCode is 9
}
// Too few refs
try {
emptySlice().loadRef(); // 0 refs!
} catch (exitCode) {
// exitCode is 9
}

10: Dictionary error

在 Tact 中,map<K, V> 类型是对 FunC 的 “hash” map 字典TL-BTVM 的底层 HashmapE 类型 的一种抽象。

如果不正确地操纵字典,如对其内存布局的错误假设, 退出码 1010 出错:`字典错误’。 请注意,除非您自己使用 Fift 和 TVM assembly ,否则您无法获得此错误:

/// Pre-computed Int to Int dictionary with two entries — 0: 0 and 1: 1
const 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 在计算阶段中结束计算, 则退出码 1313 的错误将会抛出: ` gas 错误’.

但是这个代码并不会立即按原样显示——而是应用了按位非运算,从而将值从1313变为14-14。 只有那时才显示代码。

这样做是为了防止在用户合约中人为地生成结果代码 (14-14),因为所有可以抛出退出码的函数只能指定从 006553565535 (含)范围内的整数。

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) 。 因为它也是一个 3232位签名的整数,它基本上与计算阶段的 _export 代码](#compute)具有相同的用途, 通常将结果代码称为退出码。

32: Action list is invalid

如果操作列表包含外来的cell,则操作项cell没有引用,或者某些操作项cell无法解析, 退出码 3232 出错:“行动列表无效”。

33: Action list is too long

如果排队等待执行的操作超过 255255操作阶段 将抛出错误,退出码为 3333:“操作列表太长”。

// 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 phase
repeat (256) {
nativeReserve(ton("0.001"), ReserveAtMost);
}

34: Invalid or unsupported action

此刻只有四个支持的动作:更改合约代码,发送一条消息, 保留特定数量的 nanoToncoins 并更改library cell。 如果指定动作有任何问题 (无效消息,不支持的动作等),则退出码 3434 的错误将被抛出:`无效或不支持的动作’。

// 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。 rg/develop/data forms/msg-tlb#addr_none00,或是引发此消息的合约地址, 退出码 3535 出错:`出站消息中无效的源地址’。

36: Invalid destination address in outbound message

如果出站消息中的目标地址无效,例如: 它不符合相关的 TL-B 模式,其中包含未知的工作链ID或它对给定的工作链有无效的长度, 退出码 3636 出现错误: “发送消息中无效的目标地址”。

37: Not enough Toncoin

如果带有基本模式 64设置的入站消息的所有资金已被消耗,并且没有足够的资金支付失败操作的费用,或者提供的值的 TL-B 布局(CurrencyCollection)无效,或者没有足够的资金支付转发费用,或在扣除费用后资金不足,则会抛出错误,退出码为 3737Not enough Toncoin

38: Not enough extra currencies

除了本地货币Toncoin外,TON区块链支持最多 2322^{32} 种额外货币。 它们与创建新的Jetton 不同,因为额外的货币是原生支持的——可以在发送给另一个合约的内部消息中,除了 Toncoin 金额之外,额外指定一个额外货币金额的 HashmapE。 与 Jettons 不同,额外货币只能存储和转移,没有任何其他功能。

目前,TON 区块链上没有额外的货币,但当没有足够的额外货币来发送指定数量的货币时,退出代码 3838 已被保留:没有足够的额外货币”。

39: Outbound message doesn’t fit into a cell

在处理消息时,TON 区块链会尝试根据相关的 TL-B 模式对其进行打包,如果无法打包,则会抛出退出码为 3939 的错误:Outbound message doesn't fit into a cell

40: Cannot process a message

如果没有足够的资金处理消息中的所有cell, 消息过大或它的 Merkle 深度过大, 退出码 4040 出错:`无法处理消息’。

41: Library reference is null

如果在库更改操作过程中需要库引用,但该引用为空,则会出现退出代码为 4141 的错误:“库引用为空”。

42: Library change action error

如果尝试修改库时出现错误,退出码 4242 将会丢失: `Library 更改操作错误’。

43: Library limits exceeded

如果超出库中cell的最大数目或超过Merkle树的最大深度, 退出码 4343 出错:库限制已超过

50: Account state size exceeded limits

如果账户状态(本质上是合约存储)在动作阶段结束时超过了 TON Blockchain 配置参数 43中规定的任意限制,则会抛出退出码为 5050 的错误:Account state size exceeded limits

如果配置不存在,默认值为:

  • max_msg_bits 等于 2212^{21} — 消息的最大大小(位)。
  • max_msg_cells等于2^13$ — 最高数量的 cells 一个消息可以占用。
  • max_library_cells 等于 10001000 - 可用作库参考cellscells的最大数量。
  • max_vm_data_depth 等于 292^{9} — 最大 cells 消息和帐户状态深度。
  • ext_msg_limits.max_size 等于 6553565535 - 外部信息的最大位数。
  • ext_msg_limits.max_depth 等于2 ^9$ — 最大外部消息 depth.
  • max_acc_state_cells 等于2 ^16$ — 最高数量的 cells 账户状态可以占用。
  • max_acc_state_bits 等于 216×10232^{16} \times 1023 — 帐户状态的最大大小(位)。
  • max_acc_public_libraries 等于 282^{8},即账户状态在主链上可使用的库引用单元的最大数量。
  • deleger_out_queue_size_limit 等于2 ^8$ — — 最大数量的出站消息队列(关于验证器和校验器)。

Tact 编译器

Tact 使用从 128128255255的退出码。 请注意,Tact 使用的退出代码表示在使用 Tact 生成的 FunC 代码时可能出现的合约错误,因此在事务的计算阶段而不是在编译期间抛出。

128: Null reference exception

如果有一个非空断言,例如 !!操作符,而检查值是 null,则会抛出一个退出码为 128128 的错误:“空引用异常”。

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

如果收到的内部或外部信息未被合约处理,则会出现退出码为 130130 的错误:“Invalid incoming message”。 它通常发生在合约没有特定消息的接收器及其opcode 前缀(32位整数头)。

考虑下列合约:

import "@stdlib/deploy";
contract Dummy with Deployable {}

如果尝试发送任何消息,除了 @stdlib/deploy提供的 部署,合约将没有接收器,因此会抛出错误,退出码为 130130

131: Constraints error

限制错误。 保留,但从未抛出。

132: Access denied

如果使用@stdlib/ownable库中的Ownable trait,由它提供的辅助函数 requireOwner() 会在入站消息的发件人与指定的所有者不匹配时抛出一个退出码为 132132 的错误: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

如果参数值无效或出乎意料,则会出现退出码为 134134 的错误:“无效参数”。

以下是Tact 中的一些函数,这些函数可能导致此退出码出现错误:

  1. Int.toFloatString(digits):如果 digits 不在区间:0<0 < digits <78 < 78

  2. String.fromBase64()Slice.fromBase64():如果给定的StringSlice包含非 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 封装程序中保存的代码不匹配,则会出现退出码为 135135 的错误:“未找到合约代码”。

136: Invalid address

类型 Address 的值在如下情况下有效:

  • 它占用 267267 位: 1111 位用于链 ID 前缀, 256256 位用于地址本身
  • 它属于两者之一:basechain(ID 00) 或masterchain (ID 1-1),后者需要启用 masterchain support

如果 地址无效,则会出现退出码为 136136 的错误:无效地址

// Only basechain (ID 0) or masterchain (ID -1) are supported by Tact
let 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 1-1)或以其他方式与之交互的尝试都会产生异常,退出码为 137137:“此合约未启用主链支持”。

let masterchainID = -1;
try {
// Zero address in masterchain without the config option set
dump(newAddress(masterchainID, 0));
} catch (exitCode) {
// exitCode is 137
}