Ton 区块链的官方 类ERC20-Token 智能合约代码-Transfer部分解析

news/2024/10/4 7:30:49

作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。

掘金:https://juejin.im/user/1785262612681997

GitHub : https://github.com/af913337456/

出版的书籍:

  • 《1.0-区块链DApp开发实战》
  • 《2.0-区块链DApp开发:基于公链》

Ton 区块链的官方 类ERC20-Token 智能合约代码-Transfer部分解析

最近在学习 Ton 链的智能合约,由于我之前的经验思维主要是集中在以太坊这条链的,即Solidity那套,所以带着长久偏向的思维去阅读 Ton 的合约时发现格格不入,Ton 的合约设计与EVM体系的属于天壤之别。

首先 Ton 的合约是分片的,遵循 Parent-Child 的规则,这里详细了解见:

https://blog.ton.org/how-to-shard-your-ton-smart-contract-and-why-studying-the-anatomy-of-tons-jettons

其次是合约开发的语言,Ton 有三种,用得最多的是 FunC,这是一种完全的非主流语言,在 GitHub 上都没有特点标识的那种。


按照最快了解 Token 智能合约的方式,寻找到官方的合约代码项目。由于Ton 的经济 Token 代码目前还没有类似以太坊的各种模型协议,只能把对应以太坊ERC-20的那部分取下来进行阅读。

下面我将结合Token的转账核心操作的源码来对其整个调用链路 进行细致的分析讲解,所选代码片段也有注释。

先了解合约模式
  • Ton 的合约是分片的,拿 Token 类型的合约做例子,其做法是将一份主合约,被称为 Master 或 Minter 的合约独一份进行部署,再将和 User 的子合约在转账进行时进行新建形式的一一对应部署。
  • 比如说,发布一份名叫 NOT 的 Token 合约,它的 Master 合约将被部署在链上,然后对于后续每一位收到 NOT token 的用户地址,若不存在就都会创建一份与该地址对应的子合约,称为 Wallet 合约。
  • 在 Token 类型的合约中,Master 合约中存储了 Token 的公共信息,比如 Name,Metaurl,Supply 等,而Transfer 转账行为却都发生在各自的 Wallet 合约里面。
  • 为 User 创建 Wallet 合约都要经过 Master 进行。
  • 合约允许各自内部调用,A 合约调用 B 合约的函数。

客户端-发起转账 Token 的流程

例子取于 Golang 客户端项目代码。

func main() {...// 初始化自己的钱包w, _ := wallet.FromSeed(api, words, wallet.V3R2)// 根据该 Token 的 Master 合约地址初始化 Tokentoken := jetton.NewJettonMasterClient(api, address.MustParseAddr("EQD0vdS......"))// 调用 Master 的合约函数获取转账者的 Wallet 合约tokenWallet, _ := token.GetJettonWallet(ctx, w.WalletAddress())tokenBalance, _ := tokenWallet.GetBalance(ctx)amountTokens := tlb.MustFromDecimal("0.1", 9)// 转账附带的信息comment, _ := wallet.CreateCommentCell("Hello from tonutils-go!")// 初始化收款者的地址,这不是 Wallet 地址to := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")// 在 BuildTransferPayloadV2 里指定了 OP = TransfertransferPayload, _ := tokenWallet.BuildTransferPayloadV2(to, to, amountTokens, tlb.ZeroCoins, comment, nil)// 构造链上请求的消息结构msg := wallet.SimpleMessage(tokenWallet.Address(), tlb.MustFromTON("0.05"), transferPayload)// 发送转账交易,然后结束tx, _, _ := w.SendWaitTransaction(ctx, msg)log.Println("transaction confirmed, hash:", base64.StdEncoding.EncodeToString(tx.Hash))
}

上述代码可以看到在发起转账的时候,收款地址并不是 User 的钱包地址,而是其对应的 Wallet 合约地址。这一点就和包括以太坊在内的绝大部分公链都不一样。

合约端对应的转账入口代码解析

内部消息的入口函数,根据 op 参数指定调用入口。

() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {if (in_msg_body.slice_empty?()) { ;; ignore empty messagesreturn ();}slice cs = in_msg_full.begin_parse();int flags = cs~load_uint(4);if (flags & 1) {on_bounce(in_msg_body);return ();}slice sender_address = cs~load_msg_addr();cs~load_msg_addr(); ;; skip dstcs~load_coins(); ;; skip valuecs~skip_bits(1); ;; skip extracurrency collectioncs~load_coins(); ;; skip ihr_feeint fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costsint op = in_msg_body~load_uint(32);if (op == op::transfer()) { ;; outgoing transfer;; sender_address 是一开始的转账者;; msg_value 是改次转账中的 Ton 数额send_tokens(in_msg_body, sender_address, msg_value, fwd_fee);return ();}if (op == op::internal_transfer()) { ;; incoming transfer;; my_balance 是当前所执行的合约所有者的 Ton 余额receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value);return ();}if (op == op::burn()) { ;; burnburn_tokens(in_msg_body, sender_address, msg_value, fwd_fee);return ();}throw(0xffff);
}
  • recv_internal 是系统内置的函数入口,相当于 main;
  • 系统函数还有:load_datasave_data,加载的是当前合约的数据,存储也是存储到当前合约。代码中的变量 jetton_master_address 地址永远是父合约地址
  • 转账发起时,指定 op 是 transfer,走到代码处理点 op == op::transfer,进入到 send_tokens
  • send_tokens函数源码及其解析注释内容见下👇
() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {int query_id = in_msg_body~load_uint(64);int jetton_amount = in_msg_body~load_coins();slice to_owner_address = in_msg_body~load_msg_addr(); ;; 收款人 force_chain(to_owner_address);;; owner_address 转账者,jetton_master_address Token主地址(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();;; msg_value 是改次转账中的 Ton 数额,不是 token 余额balance -= jetton_amount; ;; balance 是转账者余额,jetton_amount 是要转的数额throw_unless(705, equal_slices(owner_address, sender_address)); ;; 要求发起转账的人一致 throw_unless(706, balance >= 0); ;; 要求减去 jetton_amount 余额大于 0,防止超出cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code);slice to_wallet_address = calculate_jetton_wallet_address(state_init);slice response_address = in_msg_body~load_msg_addr(); ;; 转账结束后要被通知到的地址 cell custom_payload = in_msg_body~load_dict();int forward_ton_amount = in_msg_body~load_coins(); ;; 附属的要转的 Ton 的数额,可以是 0,客户端赋值throw_unless(708, slice_bits(in_msg_body) >= 1);slice either_forward_payload = in_msg_body;var msg = begin_cell().store_uint(0x18, 6).store_slice(to_wallet_address) ;; 走到收款人的合约处.store_coins(0) .store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1).store_ref(state_init); ;; 如果对方没被部署过 wallet 合约,那么这个消息会触发部署var msg_body = begin_cell().store_uint(op::internal_transfer(), 32) ;; internal_transfer 引导到下一个 op.store_uint(query_id, 64).store_coins(jetton_amount).store_slice(owner_address) ;; owner_address 转账者.store_slice(response_address) ;; 转账结束后要被通知到的地址 .store_coins(forward_ton_amount).store_slice(either_forward_payload).end_cell();msg = msg.store_ref(msg_body);int fwd_count = forward_ton_amount ? 2 : 1;throw_unless(709, msg_value >forward_ton_amount +;; 3 messages: wal1->wal2,  wal2->owner, wal2->response;; but last one is optional (it is ok if it fails)fwd_count * fwd_fee +(2 * gas_consumption() + min_tons_for_storage()));;; universal message send fee calculation may be activated here;; by using this instead of fwd_fee;; msg_fwd_fee(to_wallet, msg_body, state_init, 15)send_raw_message(msg.end_cell(), 64); ;; revert on errors;; 如果 send_raw_message 没出错,那么下面就会完成最后一步save_data(balance, owner_address, jetton_master_address, jetton_wallet_code); ;; 存储转账者的 token 余额
}
  • send_tokens 里面,还会进行一次内部合约调用,调用到收款人的 Wallet 合约,对应到 op 是 internal_transfer,而 internal_transfer 的处理函数是 receive_tokens
  • receive_tokens函数源码及其解析注释内容见下👇
() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure {;; NOTE we can not allow fails in action phase since in that case there will be;; no bounce. Thus check and throw in computation phase.(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();int query_id = in_msg_body~load_uint(64);int jetton_amount = in_msg_body~load_coins(); ;; token 代币余额balance += jetton_amount; ;; token 代币余额累加slice from_address = in_msg_body~load_msg_addr(); ;; 原始的 Token 转账者slice response_address = in_msg_body~load_msg_addr(); ;; 转账结束后要被通知到的地址;; sender_address 系统地址,意味着这个函数只能由系统内部调用,排除了外部调用throw_unless(707,equal_slices(jetton_master_address, sender_address)|equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address));int forward_ton_amount = in_msg_body~load_coins(); ;; 附属要转账 ton 数值int ton_balance_before_msg = my_ton_balance - msg_value;int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage());msg_value -= (storage_fee + gas_consumption());if(forward_ton_amount) { ;; 附属要转账 ton,如果不是 0msg_value -= (forward_ton_amount + fwd_fee);slice either_forward_payload = in_msg_body;var msg_body = begin_cell().store_uint(op::transfer_notification(), 32).store_uint(query_id, 64).store_coins(jetton_amount).store_slice(from_address).store_slice(either_forward_payload).end_cell();var msg = begin_cell().store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract.store_slice(owner_address) ;; 当前 Token 收款人地址.store_coins(forward_ton_amount) ;; 收款人加上这部分附属的 Ton。付款人减去.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1).store_ref(msg_body);send_raw_message(msg.end_cell(), 1);}if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) {var msg = begin_cell().store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000.store_slice(response_address) .store_coins(msg_value) ;; 超过的 Ton 手续费退款到这个地址.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1).store_uint(op::excesses(), 32).store_uint(query_id, 64);send_raw_message(msg.end_cell(), 2);};; 下面为收款人加上 Token save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
}
  • 走完 receive_tokens 之后,整个转账行为就在链上闭环了。
注意:上面的源码实现部分比较绕,务必结合注释阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/44463.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

备忘:HP Gen8服务器创建Raid

HP Gen8服务器创建Raid(there are no physical disks attached)原文地址:https://blog.51cto.com/tianhunyongheng/1606948 HP最新的X86服务器是Gen8系列,这个系列使用了ACU工具来创建Raid,这是图形化界面,可以说是更友好了。 本来通常情况下如果是买了一台新的服务器…

流畅的python--第十一章 符合 Python 风格的对象

一个库或框架是否符合 Python 风格,要看它能不能让 Python 程序 员以一种简单而自然的方式执行任务。—— Martijn Faassen Python 和 JavaScript 框架开发者 得益于 Python 数据模型,自定义类型的行为可以像内置类型那样自 然。实现如此自然的行为,靠的不是继承,而是鸭子类…

【机器学习】支持向量机(个人笔记)

目录SVM 分类器的误差函数分类误差函数距离误差函数C 参数非线性边界的 SVM 分类器(内核方法)多项式内核径向基函数(RBF)内核 源代码文件请点击此处! SVM 分类器的误差函数 SVM 使用两条平行线,使用中心线作为参考系 \(L: \ w_1x_1 + w_2x_2 + b = 0\)。我们构造两条线,…

使用PyTorch Profiler进行模型性能分析,改善并加速PyTorch训练

如果所有机器学习工程师都想要一样东西,那就是更快的模型训练——也许在良好的测试指标之后 加速机器学习模型训练是所有机器学习工程师想要的一件事。更快的训练等于更快的实验,更快的产品迭代,还有最重要的一点需要更少的资源,也就是更省钱。 熟悉PyTorch Profiler 然后就…

AI智能文案助手ChatMoney:一键打造抖音爆款视频,助你轻松吸引千万级流量!

本文由 ChatMoney团队出品引言 看着抖音上别人的视频轻松破百万点赞,是不是心里痒痒的?想知道他们是怎么做到的?其实,他们可能只是比您先一步掌握了这个秘密武器——ChatMoney。这不仅仅是一个工具,它是您抖音视频流量变现的加速器。 您是否已经厌倦了平淡无奇的文案,看着…

抖音爆款制造机!用ChatMoney,一键生成爆款视频文案,轻松获得千万流量!

本文由 ChatMoney团队出品引言 看着抖音上别人的视频轻松破百万点赞,是不是心里痒痒的?想知道他们是怎么做到的?其实,他们可能只是比您先一步掌握了这个秘密武器——ChatMoney。这不仅仅是一个工具,它是您抖音视频流量变现的加速器。 您是否已经厌倦了平淡无奇的文案,看着…

从游戏场景看,ByteHouse存算分离架构如何实现降本增效

ByteHouse 是火山引擎推出的云原生数据仓库,其存算分离架构为游戏公司提供了新解法。它将计算资源和存储资源独立扩展,实现了读写分离,优化了资源利用效率,使得整体性能提升了 4 倍以上。经过几十年发展,中国游戏产业逐步迈向成熟与稳健的新阶段。根据中国音数协游戏工委的…

AI的杀手级应用会是一个“超级能干的同事”!RAG会造就超级智能么?

什么是RAG技术 RAG是一种结合生成模型和检索系统的技术。它通过检索相关信息并将其融入生成过程,使得模型不仅依赖内部训练数据,还能利用外部信息来提升回答的准确性和广度。RAG技术的核心在于将大规模预训练的语言模型与信息检索系统(如搜索引擎或数据库)有效结合,从而增…