狗哥区块链与AI精品内容集@NonceGeek

2025年03月28日更新 75 人订阅
原价: ¥ 20 限时优惠
专栏简介 「造」最关键的是什么?| Hackathon 漫游指南(贰) Why Hackathon?| Hackathon 漫游指南(壹) 设计「众人维护」的 BuidlerBoard | BeWater.xyz Movement 白皮书中文版 Rebuidl RSS 生产因素与反生产因素 | 独立黑客创业手册(陆) 组织 | 独立黑客创业手册(伍) 生产 | 独立黑客创业手册(肆) 销售 | 独立黑客创业手册(叄) 最优先的能力 | 独立黑客创业手册(贰) 为什么从独立黑客开始 | 独立黑客创业手册(壹) Aptos Token Object V2 | Move dApp 极速入门(贰拾肆) 可編程交易塊 | Move dApp 極速入門(貳拾叁) Aptos 密鑰輪換 | Move dApp 極速入門(貳拾貳) Aptos 对象模型 | Move dApp 极速入门(贰拾壹) Aptos Moveflow SDK使用指南 | Move dApp 极速入门(贰拾) Sui 上简单 Swap 的实现 | Move dApp 极速入门(拾玖) 用 Elixir 交互 Aptos | Move dApp 极速入门(拾捌) Sui 链上数据查询 | Move dApp 极速入门(拾柒) SUI 合约测试攻略 | Move dApp 极速入门(拾陆) Sui 数据类型详解 | Move dApp 极速入门(拾伍) Airdropper Contract in Aptos | Move dApp 极速入门(拾肆) Sandwich合约源码解析 | Move dApp 极速入门(拾叁) Sui 极速上手 | Move dApp 极速入门(拾贰) scaffold-aptos 脚手架 | Move dApp 极速入门(拾壹) 对 DID Document 的思考 | Move dApp 极速入门(九) DID中地址聚合器的实现 | Move dApp 极速入门(八) 值的存取应用3.0 | Web3.0 dApp 开发(五) 合约数据类型综述 | Move dApp 极速入门(四) 操作资源 | Move dApp 极速入门(三) 第一个 Move dApp | Move dApp 极速入门(二) Hello Move | Move dApp极速入门(一) Staker | Web3.0 dApp 开发(九) Token 自动售卖机 | Web3.0 dApp 开发(七) SVG NFT 全面实践 | Web3.0 dApp 开发(六) 值的存取应用2.0 | Web3.0 dApp 开发(四) 值的存取应用1.0 | Web3.0 dApp开发(三) Scaffold-eth 快速上手 | Web3.0 dApp 开发(二) eth.build 快速上手 | Web3.0 dApp 开发(一) 1 小时理解比特币系统 【NonceGeek Workshop 0x01总结】基于链上数据生成游戏地图 Remix 完全本地化部署 NFT:实体与虚拟载体的主与辅 | 狗哥的元宇宙思辨(一) Web3Camp 内容大全@NonceGeek 用 Python 创建一条 Pow 区块链(上) 区块链与共识机制演变史 基于 Etherscan 实现 Blockchain Syncer 【论文分享】去中心化社会:寻找 Web3 的灵魂(上) 【论文分享】去中心化社会:寻找 Web3 的灵魂(下) Ted Yin | 2021 年的区块链基础设施将是什么? 0. 公链、联盟链与分布式未来(全文) 基于 Infura 与 Web3py 部署调用 Hello 合约全过程 | 以太坊开发极速入门 太上中的基因设计与Binary | 函数式与区块链(一) 理解以太坊合约数据读取过程 | 函数式与区块链(二) Hello, Ink! | 用 Rust 写智能合约(一) Mapping 数据结构 | 用 Rust 写智能合约(二) 用 Rust 程序和 Webase 交互 | Rust 学习笔记(四) 用 Sqlite 存储 WeId | Rust 学习笔记(五) 链上注册WeId与错误处理 | Rust 学习笔记(六) WeId 链上创建与本地存储的完整闭环 | Rust 学习笔记(七) 以太坊上的核心开发者 Austin | 以太坊上的最佳开发实践 1. FISCO BCOS 开发环境节点搭建全攻略 伪代码简述 ECDSA 签名过程 | 联盟链开发 WeIdentity 源码分析 | 狗哥解码 WeIdentity 源码分析 | 狗哥解码 FISCO BCOS 介绍 | 联盟链开发 给Remix升个级 | 联盟链开发 2. 控制台的安装与使用 3. 【实验】补全一个区块链应用 4. 控制台的Web化 5. Web化控制台2.0:打造团队共用区块链学习平台 6. 使用脚手架快速搭建 Java DApp 【视频+文字】分布式思维 Rebuidl RSS (EN)

可編程交易塊 | Move dApp 極速入門(貳拾叁)

  • 李大狗
  • 发布于 2023-08-23 09:38
  • 阅读 4019

Sui 是一個創新的區塊鏈平台,相比於大家熟悉的 EVM 兼容鏈,最大特色在以 Object(物件)為核心的設計、全新的智能合約語言 Sui Move。本文聚焦在 Sui 的其中一項關鍵創新:可編程交易塊 PTB(Programmable Transaction Block),探索如何透過 PTB

Ashley is a blockchain security researcher with passion for exploring new challenges and staying up-to-date with the latest technological advancements. In addition, she is also a Solidity and Move developer with experience in building DeFi protocols and conducting vulnerability mining.

Twitter: https\://twitter.com/ashleyhsu_eth

本文章奖励通过 Dorahacks 发放:

https\://dorahacks.io/zh/daobounty/318

Sui 是一個創新的區塊鏈平台,相比於大家熟悉的 EVM 兼容鏈,最大特色在以 Object(物件)為核心的設計、全新的智能合約語言 Sui Move。本文聚焦在 Sui 的其中一項關鍵創新:可編程交易塊 PTB(Programmable Transaction Block),探索如何透過 PTB 達成多元靈活的運用場景。

一句話概括可編程交易塊的概念:

可編程交易塊為一個異構且可組合的交易序列,且具有原子性。

簡單來說可以把可編程交易塊看作一個交易,在這個交易內可以調用多個智能合約的函數,或是轉移多個物件,在一個可編程交易塊(PTB)中最多可以打包 1024 個交易,這些交易是異構的,不一定是同類型的操作,可以跟 defi 交互、mint NFT或是轉帳。

在某些區塊鏈上,交易是基本的原子執行單位,若需要達成一系列操作需要智能合約來達成,例如:在同一個交易內批量發送代幣到不同地址。而在 Sui 上,基本的原子執行單位為一個可編程交易塊(PTB),以上面這個例子來說,使用 PTB 就可以在不寫智能合約的情況下達成批量發送代幣。

可編程交易塊有以下特性:

  • 可訪問鏈上所有 public function,不論是否為 entry function
  • 原子性,一個可編程交易塊中的所有操作,或者全部完成,或者全部不完成,不會結束在中間某個環節也不會部分執行某些操作。如果交易在執行過程中發生錯誤,會被回滾到交易開始前的狀態,就像這個交易從來沒有執行過一樣。
  • 前面交易的輸出可以作為後面交易的輸入,便於實現更加複雜的操作
  • 最多可以打包 1024 個交易
  • 使用 gas smashing 簡化代幣管理,gas smashing 即為自動合併 gas coin,剩餘代幣在執行結束時返回交易結果之前被合併

0x01 利用 TS SDK 使用可編程交易

1.1 安裝

npm install @mysten/sui.js

1.2 架構

// import ts sdk
import { TransactionBlock } from "@mysten/sui.js";

const txb = new TransactionBlock();

// Transfer the object to a specific address.
txb.transferObjects([tx.object(objectId)], txb.pure("0xSuiAddress"));

// other operations
// ...

// execute
signer.signAndExecuteTransactionBlock({ transactionBlock: txb });

1.3 可用交易類型

PTB 支援將以下不同類型的交易打包、鏈接在一起,創建一個適合應用程序需求的自定義原子交易塊。

  • txb.splitCoins(coin, amounts) - 從提供的 coin 分割出指定數量的 coin
    • Example: 從 gas coin 分割出兩個 coin 物件分別有 100 sui 及 200 sui
    • txb.splitCoins(txb.gas, [txb.pure(100), txb.pure(200)])
  • txb.mergeCoins(destinationCoin, sourceCoins) - 合併 coin
  • txb.transferObjects(objects, address) - 轉移物件所有權到指定地址
  • txb.moveCall({ target, arguments, typeArguments }) - 執行一個 Move call
  • txb.makeMoveVec({ type, objects }) - 構造一個 vector 包含 object,用來作為 moveCall 的輸入
  • txb.publish(modules, dependencies) - 部署 Move package

1.4 構建輸入

可編程交易的輸入主要有兩種:object 或 value。分別用以下兩種方法構建:

  • object:txb.object(objectId)
  • value:txb.pure(rawValue)

在瞭解了可編程交易塊的概念及特性之後,就來看看一些範例與實操吧!

0x02 Example1 - 空投 SUI 到多個地址

有時候有些相同類型的處理你不會特別想部署一個合約處理,例如大規模鑄造 NFT 或是向多方發送代幣,這時候將交易打包到可編程交易塊顯然是一個很好的選擇。以下提供兩種作法:

2.1 Scallop Tools

Scallop Tools 是一款 UI 工具,可以將多個交易打包到一個可編程交易塊,目前支援 transfer objects/coins、merge/split coin。對於不會使用 TS SDK 的用戶也能輕鬆構建一個可編程交易塊。

image.png

2.2 TS SDK

定義要傳送給哪個地址 (recipients)、多少數量的 coin (amounts)

let recipients = ["0x123", "0x456"];
let amounts = [100000000, 100000000];

分割出不同數量的 coin,用於接下來的傳送

const tx = new TransactionBlock();
const coins = tx.splitCoins(
  tx.gas,
  amounts.map((amount) => tx.pure(amount))
);

遍歷 recipients,傳送 sui coin 到指定地址

recipients.forEach((recipient, index) => {
  tx.transferObjects([coins[index]], tx.pure(recipient));
});

執行

signer.signAndExecuteTransactionBlock({ transactionBlock: tx });

完整源碼請見 github repo。

0x03 Example2 - 在 Bucket Protocol 上使用閃電貸開槓桿

3.1 背景介紹

Bucket Protocol 是 SUI 上的 CDP 協議,可超額抵押 SUI/ETH/USDC/USDT 並借出原生穩定幣 BUCK。Bucket 也提供閃電貸服務,可借出 BUCK/SUI 等代幣,收取萬分之五的手續費。

3.2 Bucket Protocol 的閃電貸(Flashloan)

閃電貸是一種無需抵押品便可以借到錢的方式,借款金額基本上無上限,取決於協議池子提供多少代幣可供借款。唯一的限制是必須要在同一個交易內將貸款及手續費還回去,如果借款人在交易結束前沒有還款,該交易會 abort。對協議方來說閃電貸基本上是零風險。

提到閃電貸就會提到一個 Sui 特有的 pattern 稱為 Hot Potato,通常會運用 Hot Potato 的特性來實作閃電貸。Hot Potato 是一個没有任何能力修飾符的 struct,因此它只能在其模塊中被打包和解包。如果函數 A 返回這樣的一個結構,則必須要有另一個函數消耗它。

以 Bucket protocol 中閃電貸函數 flash_borrow_buck 來舉例:

public flash_borrow_buck<Ty0>(Arg0: &mut BucketProtocol, Arg1: u64): Balance<BUCK> * FlashReceipt<BUCK, Ty0> {
    ...
}

flash_borrow_buck 返回了一個 Balance,也就是你借出來的 BUCK,加上一個 FlashReceipt 是你的借條,可以看到 FlashReceipt 沒有任何修飾符:

struct FlashReceipt<phantom Ty0> {
    amount: u64,
    fee: u64
}

閃電貸還款函數flash_repay_buck 需傳入 BucketProtocol 物件、BUCK、FlashReceipt 作為輸入。FlashReceipt在此函數被消耗掉。

public flash_repay_buck<Ty0>(Arg0: &mut BucketProtocol, Arg1: Balance<BUCK>, Arg2: FlashReceipt<BUCK, Ty0>) {
    ...
}

在 sui explorer 上查看 buck 模組:https\://suiexplorer.com/object/0xce7ff77a83ea0cb6fd39bd8748e2ec89a3f41e8efdc3f4eb123e0ca37b184db2?module=buck\&network=mainnet

可以知道,以上操作是無法透過 cli 去執行,但是可以透過可編程交易塊或寫智能合約來達成。

3.3 流程

假如現在有 1 sui,想要做循環借貸,假設 BUCK/SUI = 1,LTV = 69%。

  1. 1 SUI 存入 bucket 借出 0.69 BUCK。
  2. 0.69 BUCK 換出 0.69 SUI。

重複以上過程。

通過以上流程,最後你會有:

  • asset = 1+0.69+0.69*0.69 = 1/(1-0.69) = 3.2
  • debt = 0.69/(1-0.69) = 2.2
  • leverage = 3.2/1 = 3.2

不過還是要注意 bucket 的倉位是至少要借出 10 BUCK,一次性的 borrow fee 會在 0.5%\~5% 之間浮動,MCR = 110%,如果倉位 CR<110% 會被清算。

Bucket 很貼心的提供了閃電貸,循環借貸可以通過閃電貸來操作。注意會有閃電貸手續費以及 DEX 的 swap 手續費。

這邊簡單整理在 bucket 用閃電貸開槓桿的操作,假設我們最初有 10 SUI:

  1. 閃電貸借出價值 20 SUI 的 BUCK
  2. 在 dex swap 出 SUI
  3. 在 bucket deposit SUI 並借出 BUCK
  4. 閃電貸還款

3.4 實操

這邊使用了tx.moveCall調用了閃電貸函數,主要確定有填好 target 以及參數。

target 的格式為 {package}::{module}::{function},以下面這個 target 來說:

FLASH_BORROW_BUCK_TARGET = "0x9e3dab13212b27f5434416939db5dec6a319d15b89a84fd074d03ece6350d3df::buck::flash_borrow_buck"

typeArguments 即為 generic,填入 sui 是因爲選擇在 sui tank 借出代幣。

  const [buck_balance, flash_receipt] = tx.moveCall({
    target: FLASH_BORROW_BUCK_TARGET,
    typeArguments: ["0x2::sui::SUI"],
    arguments: [tx.object(PROTOCOL_OBJECT), buck_amount],
  });

借出 BUCK 後我選擇在 Cetus 的 BUCK-SUI pool,將 BUCK swap 為 SUI,實際操作時還是要注意一下滑點:

  tx.moveCall({
    target: CETUS_SWAP_TARGET,
    typeArguments: [BUCK_TYPE, "0x2::sui::SUI"],
    arguments: [
      tx.object(CETUS_GLOBAL_CONFIG),
      tx.object(CETUS_BUCK_SUI_POOL),
      vec,
      tx.pure(true),
      buck_balance_value,
      tx.pure(0),
      tx.pure(4295048016),
      tx.object("0x6"),
    ],
  });

在 bucket protocol 開一個倉位,這邊會借出 BUCK:

  const buck_output_balance = tx.moveCall({
    target: BORROW_TARGET,
    typeArguments: ["0x2::sui::SUI"],
    arguments: [
      tx.object(PROTOCOL_OBJECT),
      tx.object(ORACLE_OBJECT),
      tx.object(CLOCK_OBJECT),
      sui_balance,
      borrow_buck_amount,
      tx.pure([]),
    ],
  });

最後償還閃電貸:

  tx.moveCall({
    target: REPAY_FLASH_BORROW_TARGET,
    typeArguments: ["0x2::sui::SUI"],
    arguments: [tx.object(PROTOCOL_OBJECT), buck_output_balance, flash_receipt],
  });

如果交易不是真的要上鏈的話,可以使用 dryRunTransactionBlock查看結果:

  const response = await signer.dryRunTransactionBlock({
    transactionBlock: tx,
  });

完整源碼請見 github repo:

https://github.com/ashirleyshe/sui-programmable-transaction-blocks/blob/main/scripts/bucket_leverage_flashloan.ts

0x04 總結

PTB 強大的結構可以將一系列交易打包在一起,創建一個自定義原子交易塊,滿足各種應用程序的需求。moveCall 可以調用任何鏈上的公共函數,極大的增強了 Sui Move 的靈活度及通用性。

0x05 Reference

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论