随着区块链技术的飞速发展,TON生态系统逐渐崭露头角,吸引了大量开发者和投资者的关注。然而,随之而来的安全问题也不容忽视。为了确保TON生态的安全性和可靠性,专业的安全审计成为不可或缺的一环。本文将深入探讨Beosin如何对TON生态进行全方位的安全审计,护航其全生态安全。
权限校验
由于TON区块链在一定程度上实现了账本和逻辑的分离,TON合约的调用通常比以太坊合约更加复杂。以Jetton钱包为例,一个Jetton Master合约管理着多个用户钱包,每个用户的钱包合约记录各自的余额。在进行铸币操作时,通常涉及多个合约的调用,因此需要特别注意合约之间的权限校验。
这里举两个例子:
第一种是Master合约调用Wallet合约的情况。由于一个Master合约对应多个Wallet合约,可以直接在Wallet合约中用Master地址进行判断。例如,要求sender_address等于master_address,如下代码所示:
throw_unless(error::unauthorized_transfer, equal_slice_bits(master_address, sender_address));
第二种是Wallet合约调用Master合约的情况。由于一个Master合约对应多个Wallet合约,不能直接使用上述方法进行鉴权。因此,Wallet合约调用Master合约时,通过from_address、my_address和jetton_wallet_code计算出Wallet合约地址,然后判断sender_address是否等于计算出的Wallet合约地址。
throw_unless(error::unauthorized_burn_request, equal_slice_bits(calc_user_wallet(from_address, my_address(), jetton_wallet_code), sender_address) );
溢出
在TON区块链的Func语言中,鉴于其仅支持 int 类型,在审计过程中务必考虑这一点。通过审计,我们发现大多数代码都是在转账后进行检查,因此我们建议在转账前进行检查。下面是我们编写的一个简单示例。
() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure inline_ref {int query_id = in_msg_body~load_query_id();int jetton_amount = in_msg_body~load_coins();slice to_owner_address = in_msg_body~load_msg_addr();force_chain(to_owner_address); (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();throw_unless(error::not_enough_jettons, balance >= jetton_amount);throw_unless(error::not_enough_jettons, jetton_amount >= 0);balance -= jetton_amount;
当然,还可以使用反序列化中的 store_coins 来保存 amount。store_coins 要求输入的 amount 必须为正数,这也可以有效防止溢出。
并发消息调用和锁的安全性
在TON区块链上支持消息的并发调用。并发调用指的是存在多个用户同时对账本进行操作,而不受先后顺序的限制。然而,在某些情况下,如果存在多个用户同时对账本进行操作,可能会出现问题。这时需要使用锁机制,以确保用户的调用有先后顺序。
简单展示了TON区块链中并发消息调用和锁机制防范的流程:
1.User A 和 User B 分别发送消息到 Master Contract。
2.Master Contract 管理多个钱包合约(Wallet Contract A 和 Wallet Contract B),并包含锁机制逻辑以确保顺序性。
3.Wallet Contract A 和 Wallet Contract B 分别处理交易。
箭头表示消息和调用的流程,通过在Master Contract中实现的锁机制,确保了安全的并发操作。
cell 数据解析
在TON区块链中,Cell是一种灵活且高效的树状数据结构,用于存储和传输数据,是构建和解析TON区块链数据的基本单位。
因此,在审计中需要特别注意Cell数据解析的正确性。在Func语言中,解析Cell数据是顺序进行的。
常用的解析操作和函数包括:读取位(Bits)操作,如 load_bits(n) 从Slice中读取n位数据和 skip_bits(n) 跳过n位数据;读取整数(Integers)操作,如 load_uint(n) 读取n位无符号整数和 load_int(n) 读取n位有符号整数;读取字节(Bytes)操作,如 load_bytes(n) 从Slice中读取n个字节;读取引用(References)操作,如 load_ref() 读取一个引用并返回一个新的Cell对象;读取地址(Address)操作,如 load_msg_addr() 读取一个消息地址;读取代币(Coins)操作,如 load_coins() 读取一个代币值。此外,还包括Slice操作,如 slice_empty?() 检查Slice是否为空和 begin_parse() 创建一个新的Slice用于解析引用的Cell。
下面举个例子:
(int, slice, int) parse_header(cell message) inline_ref {slice cs = message.begin_parse();int flags = cs~load_uint(4);slice sender_address = cs~load_msg_addr();cs~load_msg_addr();cs~load_coins();cs~skip_bits(1);cs~load_coins();int fwd_fee = muldiv(cs~load_coins(), 3, 2);return (flags, sender_address, fwd_fee);}
开始解析:调用 message.begin_parse() 方法创建一个用于解析的 slice 对象 cs。
读取标志位:使用 cs~load_uint(4) 读取4位无符号整数,并将其存储在 flags 变量中。
读取发送者地址:使用 cs~load_msg_addr() 读取消息发送者的地址,并将其存储在 sender_address 变量中。
跳过目的地址:再次调用 cs~load_msg_addr() 读取但忽略目的地址。
跳过消息值:使用 cs~load_coins() 读取但忽略消息值。
跳过额外货币收集标志:使用 cs~skip_bits(1) 跳过1位的额外货币收集标志。
跳过 IHR 费用:使用 cs~load_coins() 读取但忽略IHR费用。
读取并计算转发费用:使用 cs~load_coins() 读取硬币值,然后通过 muldiv(cs~load_coins(), 3, 2) 计算转发费用 fwd_fee。
费用回收
在 TON 区块链中,"excesses" 指的是在消息处理过程中产生的未使用的费用。用户在发送交易时,会预留一定量的 gas 费用用于支付计算资源,以及可能的存储费用。如果实际消耗的费用低于预留的金额,那么未使用的部分就会成为 excesses。这些 excesses 可能来源于未使用完的 gas 费用或低于预留的存储费用。在审计过程中,需要特别关注 excesses 的处理,确保多余的费用能够正确退还给用户。
消息手续费
Ton中的手续费取决于许多因素,并且最终的手续费是由多种不同的交易费构成.在审计过程中,应特别注意手续费计算的准确性,以避免因 gas 不足导致消息失败。
transaction_fee = storage_fees + in_fwd_fees + computation_fees + action_fees + out_fwd_fees名称介绍示例storage_fees区块链上存储智能合约而支付的金额TON钱包也是一个智能合约,每次接收或发送交易时都会支付存储费用in_fwd_fees区块链外部导入消息的费用。每次进行交易时,都必须将其传送给将处理它的验证者每个钱包应用程序(如Tonkeeper)进行的每笔交易都需要首先在验证节点之间分发,支付这笔费用computation_fees在虚拟机中执行代码而支付的金额。代码越大,必须支付的费用就越多。每次使用钱包(即智能合约)发送交易时,都会执行钱包合约的代码并为此付费。action_fees智能合约发送外部消息所收取的费用。智能合约间的相互调用需要为此付费。out_fwd_fees从TON区块链发送消息到外部服务(例如,日志)和外部区块链的费用。由于尚未实施,因此目前未使用。因此目前等于0。
以下是常见费用的参考:
发送任何数量的TON的平均费用为0.0055 TON。发送任何数量的自定义Jettons的平均费用为0.037 TON。铸造一个NFT的平均费用为0.08 TON。在TON上保存1 MB数据一年的成本为6.01 TON。
关于每种费用的详细计算公式可以参考:
https://docs.ton.org/mandarin/develop/smart-contracts/fee-calculation
考虑消息失败的情况
在TON的函数调用中,一个操作可能会涉及多个消息的发送。当某个消息失败时,之前执行的消息结果不会受到影响。如果不回滚之前消息更新的数据,可能会导致数据不一致问题。例如,在抵押操作中,用户首先将资金转入合约,但由于后续消息的失败且代码未考虑消息失败的情况,资金未能返还给用户。与EVM不同的是,TON的函数调用不会在失败时自动回滚到最初状态,这需要开发人员手动处理回滚逻辑,以确保数据一致性。
数据导入顺序
在TON环境中,由于消息传输的异步特性,如果没有考虑数据导入的顺序,这会导致数据不一致的情况。例如,当用户执行赎回操作时,如果其他用户在此之前执行了类似的资产操作,可能会导入新的资产地址数据。如果在执行赎回时未及时更新相关的地址信息,那么可能导致赎回的资产与实际情况不符。
消息反弹
在TON中,处理消息反弹(bounce)是一项重要任务,因为消息传递失败会影响智能合约的正常运行和业务逻辑。对于什么时候该反弹什么时候不反弹的处理需要根据当前业务逻辑进行仔细审计。
几乎所有在智能合约之间发送的内部消息都应该是可弹回的,即应该设置它们的“bounce”位。所有智能合约都应检查所有入站消息的“bounced”标志,并且要么默默接受它们(通过立即以exit code 0终止),要么执行一些特殊处理来检测哪个出站查询失败了。弹回消息主体中包含的查询永远不应执行。
但是在某些情况下,必须使用不可弹回内部消息。例如,没有发送至少一条不可弹回内部消息给它们,就无法创建新账户。除非这条消息包含一个带有新智能合约的代码和数据的StateInit,否则在不可弹回内部消息中拥有非空主体是没有意义的。non-bounce消息的设计目的是为了避免某些情况下的资源浪费和处理复杂性。
TON生态系统作为一个开放且去中心化的平台,吸引了大量开发者和用户。然而,其复杂的技术架构和广泛的应用场景也带来了潜在的安全风险。
Beosin在TON生态中的成功案例众多,Beosin此前已对TON生态Stablecoins 龙头项目Aqua Protocol进行了详尽的安全审计。审计内容涵盖了智能合约代码的安全性、 业务实现逻辑的正确性、合约代码gas优化、潜在漏洞的发现和漏洞修复等多个方面。
在多轮严格的审计和复查之后,Beosin确认Aqua Protocol的代码实现安全可靠,所有安全漏洞已经得到修复,业务逻辑实现与设计文档相匹配。同时,Aqua Protocol团队积极响应审计过程中提出的潜在风险,迅速进行修复和优化,确保协议达到行业内最高的安全标准。
在区块链技术不断发展的今天,安全问题已成为制约其发展的重要因素。Beosin希望通过专业的安全审计,不仅帮助项目方发现和修复潜在的安全问题,还为TON生态系统的健康发展提供了有力支持。未来,随着TON生态的不断壮大,Beosin将继续发挥其专业优势,为其提供更加全面和深入的安全保障。