TON智能合约的安全隐患与优化建议
区块链领域的技术迭代速度很快,TON(The Open Network)这个平台靠着高效和灵活的设计,开始吸引越来越多开发者的目光。它的底层架构和独特的运行方式,确实给去中心化应用(dApp)的搭建提供了不少新工具和可能性。
不过话说回来,功能越复杂,智能合约的安全门槛也跟着水涨船高。FunC 作为 TON 上编写智能合约的主要语言,虽然灵活、效率高,但它同样藏着不少容易踩坑的地方。想写出既安全又靠谱的合约,光会写代码还不够,还得真正搞懂 FunC 的设计思路,以及那些你可能压根没意识到的风险点。
这篇文章会从头梳理 TON 区块链上和智能合约相关的一些关键特性,再重点聊聊那些容易被忽略的漏洞,帮新手少走弯路。
TON 的异步机制和账户设计到底是怎么回事
智能合约之间的异步调用
网络分片与异步通信
TON 的区块链结构分成三层:主链(Masterchain)、工作链(Workingchain)和分片链(Shardchain)。
主链是整个网络的大脑,负责存全局的元数据,也管共识。它会记录所有工作链和分片链的状态,保证整个系统安全且一致。工作链可以理解成独立的“小链”,最多能到 2^32 条,每条可以跑特定类型的交易和合约,规则也可以自己定。分片链则是工作链下面的细分单元,用来分担负载、提升处理速度。每个工作链理论上能拆成 2^60 个分片链,它们各自处理一部分交易,实现真正的并行。
从理论上讲,每个账户都可以独占一个分片链,独立维护自己的 COIN 或 TOKEN 余额,账户之间的交易完全可以并行处理。账户之间靠异步消息沟通,消息在不同分片链之间传递的路径长度是 log_16(N) - 1,N 是分片链的数量。
图源:https://frontierlabzh.medium.com/ton-web3世界的weixin-e1d3ae3b3574
在 TON 里,智能合约是通过发消息和收消息来互动的。消息分两种:内部消息(一般是合约之间互相发)和外部消息(从合约外面发进来的)。发消息不用等对方立刻回复,发送方可以接着跑后面的逻辑。这种异步传消息的方式,跟以太坊那种同步调用比起来,灵活性和扩展性都强不少,至少不会因为等回复而卡住整个流程。但反过来,它也带来了并发处理和竞争条件的新麻烦。
消息长什么样?结构怎么定的?
TON 的消息一般包含发件人、收件人、金额和消息体。消息体可以是调用某个函数、传点数据,或者别的自定义内容。消息格式可以自己灵活定义和扩展,所以不同合约之间能高效地传各种信息。
消息队列和状态处理
每个合约都维护着一个消息队列,存着还没处理的消息。合约执行的时候,会按顺序一条条处理队列里的消息。因为是异步的,合约的状态不会在收到消息的那一刻立刻更新。
异步消息的好处
•跟分片很搭:TON 的异步机制跟它的分片设计高度匹配。每个分片各自处理合约的消息和状态变化,不用为了跨分片同步通信而浪费时间,这样整个网络的吞吐量和扩展性都上去了。
•省资源:异步消息不需要马上响应,合约的执行可以分散在多个区块里完成,不会让一个区块负担太重。这也让 TON 能承载更复杂、更耗资源的智能合约。
•容错性不错:如果某个合约因为资源不够或者其他原因没能及时回消息,发送方照样可以继续处理其他逻辑,系统不会因为一个合约卡住就瘫痪。
异步合约设计会遇到什么问题
•状态一致性问题:消息是异步来的,合约的状态在不同时刻可能收到不同的消息,这特别容易搞乱状态一致性。设计合约时,必须想清楚不同消息顺序可能带来什么变化,保证不管什么情况系统都能保持一致。
•竞争条件与防护:异步消息处理很容易出现竞争条件——多个消息可能同时想改合约状态。开发者得引入合适的锁机制,或者用事务操作来避免状态冲突。
•安全性考量:异步合约在做跨合约通信时,容易碰上中间人攻击或者重放攻击。设计的时候一定得把这些安全风险考虑进去,比如加个时间戳、随机数或者多重签名来防范。
账本模型是怎么设计的
TON 在构建区块链基础设施时,采用了一种挺特别的账户抽象和账本模型。这种模型的灵活性体现在它怎么管账户状态、怎么传消息、怎么执行合约。
账户抽象
TON 的账户模型是基于合约的抽象,每个账户都可以看成一个合约,跟以太坊的账户抽象有点像,但更灵活更通用。在 TON 里,账户不只是装资产的容器,它还带着合约代码和状态数据。每个账户都由代码(Code)、数据(Data)和消息处理逻辑(Message Handling)组成。
账户结构:每个 TON 账户都有一个唯一地址,这个地址是账户代码的哈希值、部署时的初始数据以及其他一些参数拼起来的。也就是说,同样的代码和初始数据,在不同环境(比如不同区块链或分片)里部署,可能会生成不一样的地址。
灵活性:因为每个账户都能跑自己的合约代码,TON 的账户可以实现很复杂的逻辑。它不只是一个简单的余额容器,还能处理复杂的状态转移、跨账户的消息通信,甚至根据特定条件自动操作。这让 TON 的账户模型比传统区块链的账户模型扩展性和灵活性都强。
账本结构
TON 的账本结构设计出来就是为了高效处理大规模并发交易,支持异步消息和多分片操作。每个账户的状态都存在 Merkle 树结构里,这样账本的状态验证效率很高。
状态存储
账户的状态信息存在持久化存储里,用 Merkle 树组织起来,保证状态的完整和安全。这种设计也方便高效查询和验证,尤其是跨分片交易的时候。
账户或智能合约状态一般包含这些内容:
1.基础货币的余额
2.其他货币的余额
3.智能合约代码(或者它的哈希)
4.智能合约的持久化数据(或者它的 Merkle 哈希)
5.关于持久化存储单元数和用了多少原始字节数的统计
6.智能合约持久存储的付款最近时间(其实是主链块号)
7.转移货币并从这个账户发消息需要的公钥(可选,默认等于 account_id 本身)。某些情况下,类似比特币交易输出那样,这里可以放更复杂的签名检查代码,这时候 account_id 就等于这个代码的哈希。
不是每个账户都需要所有这些信息。比如智能合约代码只对智能合约有用,“简单”账户用不上。而且,虽然任何账户都得有基础货币的非零余额(比如基本工作链的主链和分片链的 Gram),但其他货币的余额可以为零。为了避免保留没用的数据,在工作链创建的时候会定义一种 sum-product 类型,用不同的标记字节来区分不同的“构造函数”。最终,账户状态本身被存成 TVM 持久化存储的单元集合。
消息传递与处理
TON 的账本结构内置了异步消息传递的支持,每个账户可以独立处理收到的消息并更新状态。这种异步机制允许账户之间进行复杂的交互,而不会因为某个操作延迟就拖累其他账户。
Gas 模型
TON 区块链通过它独特的 Gas 费模型,大幅优化了智能合约的执行效率。Gas 费模型在区块链里用来衡量和限制合约执行时消耗的资源。跟以太坊那种传统 Gas 模型比,TON 的设计更复杂也更高效率,能更精确地管好合约执行过程中的资源消耗。
细化的 Gas 消耗测量
TON 的 Gas 模型能精确测量合约执行时消耗的计算资源、存储操作和消息传递成本。通过细化这些资源的测量,TON 的 Gas 模型能防止某些复杂度过高的操作占用太多资源。通过限制 Gas 消耗,TON 确保网络每个节点都能公平分配计算资源,避免单一合约或操作过度消耗网络资源。
并行处理与 Gas 优化
TON 支持智能合约并行处理,多个合约可以同时在不同分片上跑,不会互相堵住。在这种设计下,Gas 模型跟并行执行和分片机制紧密结合,通过在多个分片上并行处理合约,TON 能把 Gas 的计算和支付分散到不同节点和链上,避免网络拥堵,同时最大化资源利用率。
动态 Gas 调整机制
TON 的 Gas 模型里还有动态调整机制,能根据网络的实时负载情况调整 Gas 费。这意味着网络负载低的时候,用户可以用更低的 Gas 费执行合约,鼓励大家在低负载时段操作,平衡网络资源使用。这种机制既提升了用户体验,也通过市场化的方式控制了资源使用峰值。
TON 智能合约里容易忽略的漏洞
之前我们写过一篇 TON 安全分析文章,已经详细介绍了 TON 生态里常见的漏洞,可以看下面这个表参考一下:
这篇文章重点说说我们团队总结出来的那些容易被忽略的漏洞点:
(1) 代码可读性优化
在 TON 的智能合约里,经常直接用数字来存消息发送相关的数据。比如下面这段代码,多次用数字表示标识和数据长度,这让代码可读性很差,也不好维护。别的开发者看这些代码时,很难猜出这些数字是什么意思、干什么用的。建议把关键数字定义成常量,比如把 0x18 写成 NON_BOUNCEABLE。
另外,合约判断条件里的错误提示信息,也建议定义成变量来代替错误码。
(2)用 end_parse() 确保数据完整性
在 TON 合约里,数据解析是固定顺序的,从原始数据里一步步加载指定类型的数据。这种方式保证了数据的一致和准确。看下面这个例子:
这里的 end_parse() 用来检查数据切片(slice)是不是空的。如果切片不为空,函数会抛出一个异常。这样可以确保数据格式和内容都符合预期。如果 end_parse() 发现数据切片里还有剩余数据,说明数据解析可能没按预期走,或者数据格式有问题。所以调用 end_parse() 能检查解析过程中有没有遗漏或异常。
(3)数据存和取的类型不匹配会引发异常
这里主要说的是 int 和 uint 的存取类型要匹配。比如下面这段代码,存数据时用了 store_int() 来存 int 类型的值 -42,但取数据时却用了 load_uint(),这样就可能出异常。
(4)合理使用 inline_ref 和 inline 修饰符
先说说 inline 和 inline_ref 的区别:
lInline:用 inline 修饰符的函数,代码会在每次调用时直接插入到调用位置。也就是说,每次调用函数,实际代码会被复制到调用位置,而不是像普通函数那样跳到函数体执行。
linline_ref:用 inline_ref 修饰符的函数,代码存在一个独立的 cell 里。每次调用时,TVM 通过 CALLREF 命令来执行存在 cell 里的代码,而不是在调用位置插入函数代码。
所以,inline 适合简单的函数,能减少调用开销,但可能导致合约代码重复;inline_ref 适合比较复杂的、或者被多次调用的函数,通过把代码存在单独 cell 里提高效率,避免重复。总结一下:函数比较大或者多个地方调用时,建议用 inline_ref;反之,用 inline。
(5)确定正确的工作链
TON 允许创建多达 2^32 条工作链,每条工作链又能细分出 2^60 个分片,但目前在用的一共只有两条工作链:主链(-1)和基本链(0)。合约在计算目标地址时,必须明确指定目标地址所属的链 ID,确保生成的 wallet 地址落在正确的工作链上。为了避免生成错误地址,建议用 force_chain() 强制指定链 ID。
(6)避免错误码冲突
合约设计里,错误码的管理很关键,能保证规范、避免混淆。对于 TON 智能合约,首先得确保每个错误码在合约里是唯一的,不能重复定义,不然容易混淆、信息不明确。其次,TON 平台或者底层系统已经定义了一些标准错误码,得避开这些系统错误码,比如 333 错误码表示链 ID 不匹配。所以建议合约的错误码最好定在 400 到 1000 之间。
(7)操作完成后需要存数据和调用 return()
在 TON 智能合约里,消息处理会根据 op-code 选择不同的逻辑。完成对应业务逻辑后,还必须做两件事:首先,如果涉及数据更改,必须调用 sa ve_data() 确保数据被存下来,否则改了的也没用;其次,必须调用 return() 表示操作完成,否则会触发 throw(0xffff) 异常。
总的来说,TON 区块链靠着创新的架构和灵活的开发环境,正逐渐成为去中心化应用开发者的一个不错选择。
不过,随着智能合约在 TON 生态里越来越重要,合约安全问题可不能马虎。开发者得深入了解 TON 的特性,严格遵循最佳实践,加强安全审计,保证合约稳健又安全。
现在 TON 生态发展得很快,吸引了不少资金和活跃用户。但随之而来的
安全问题也得认真对待
风险声明:
-
- 元宵节猜灯谜的祝福短信
- 角色扮演 |
-
- 关于柯南的沙雕网名有哪些
- 角色扮演 | 1
- 网名
-
- 最新中性名字男女通用网名有哪些
- 角色扮演 | 1
- 网名
-
- 关于蓝色说唱的网名有哪些
- 角色扮演 | 1
- 网名
-
- 我好喜欢你是什么梗?
- 角色扮演 |