{"content":{"title":"Move中的设计模式(3)——Hot Potato","body":"> 我翻译并补充了英文版的Patterns，欢迎以下链接查看  \r\n> 原文链接：https://blog.chrisyy.top/move-patterns/hot-potato.html\r\n> 仓库链接（欢迎提Issue和Pr）：https://github.com/chrisyy2003/move-patterns\r\n\r\n# Hot Potato\r\n\r\n|||\r\n|-|-|\r\n| **Name** | Hot Potato |\r\n| **Origin** | [Sui Project](https://github.com/MystenLabs/sui/blob/20e68787b3ace2b408ba0c1d8d9117fc5206cb05/sui_programmability/examples/defi/sources/FlashLender.move#L19) / [Todd Nowacki](https://github.com/tnowacki) |\r\n| **Example** | [FlashLender.move](https://github.com/MystenLabs/sui/blob/a6156aeaf332b9f257cf04063a9a751a7a431360/sui_programmability/examples/defi/sources/flash_lender.move) |\r\n| **Depends on** | None |\r\n| **Known to work on** | Move |\r\n\r\n## 概述\r\n\r\nHot Potato模式受益于Move中的Ability，Hot Potato是一个没有`key`、`store`和`drop`能力的结构，强制该结构在创建它的模块中使用掉。这种模式在闪电贷款这样的需要原子性的程序中是理想的，因为在同一交易中必须启动和偿还贷款。\r\n\r\n```move\r\nstruct Hot_Potato {}\r\n```\r\n\r\n相较于Solidity中的闪电贷的实现，Move中的实现是优雅的。在Solidity中会涉及到较多的动态调用，并且存在重入，拒绝服务攻击等问题。但在Move中，当函数返回了一个不具有任何的ability的potato时，由于没有drop的ability也，所以没办法储存到全局里面去，也没有办法去储存到其他结构体中。在函数结束的时也不能丢弃，所以必须解构这个资源，或者传给另外一个可以使用这个potato的一个函数。\r\n\r\n所以通过这个方式，可以来实现函数的调用流程。模块可以在没有调用者任何背景和条件下，**保证调用者一定会按照预先设定的顺序去调用函数**。\r\n\r\n> 闪电贷本质也是一个调用顺序的问题\r\n> \r\n\r\n## 如何使用\r\n\r\n### Aptos\r\n\r\nAptos上[Liqudswasp](https://github.com/pontem-network/liquidswap/blob/main/sources/swap/liquidity_pool.move#L99)项目实现了FlashLoan，这里提取了核心的代码。\r\n\r\n```move\r\npublic fun flashloan<X, Y, Curve>(x_loan: u64, y_loan: u64): (Coin<X>, Coin<Y>, Flashloan<X, Y, Curve>)\r\n    acquires LiquidityPool, EventsStore {\r\n        let pool = borrow_global_mut<LiquidityPool<X, Y, Curve>>(@liquidswap_pool_account);\r\n        ...\r\n        let reserve_x = coin::value(&pool.coin_x_reserve);\r\n        let reserve_y = coin::value(&pool.coin_y_reserve);\r\n        // Withdraw expected amount from reserves.\r\n        let x_loaned = coin::extract(&mut pool.coin_x_reserve, x_loan);\r\n        let y_loaned = coin::extract(&mut pool.coin_y_reserve, y_loan);\r\n        ...\r\n        // Return loaned amount.\r\n        (x_loaned, y_loaned, Flashloan<X, Y, Curve> { x_loan, y_loan })\r\n    }\r\n\r\npublic fun pay_flashloan<X, Y, Curve>(\r\n        x_in: Coin<X>,\r\n        y_in: Coin<Y>,\r\n        loan: Flashloan<X, Y, Curve>\r\n    ) acquires LiquidityPool, EventsStore {\r\n        ...\r\n        let Flashloan { x_loan, y_loan } = loan;\r\n\r\n        let x_in_val = coin::value(&x_in);\r\n        let y_in_val = coin::value(&y_in);\r\n\r\n        let pool = borrow_global_mut<LiquidityPool<X, Y, Curve>>(@liquidswap_pool_account);\r\n\r\n        let x_reserve_size = coin::value(&pool.coin_x_reserve);\r\n        let y_reserve_size = coin::value(&pool.coin_y_reserve);\r\n\r\n        // Reserve sizes before loan out\r\n        x_reserve_size = x_reserve_size + x_loan;\r\n        y_reserve_size = y_reserve_size + y_loan;\r\n\r\n        // Deposit new coins to liquidity pool.\r\n        coin::merge(&mut pool.coin_x_reserve, x_in);\r\n        coin::merge(&mut pool.coin_y_reserve, y_in);\r\n        ...\r\n    }\r\n```\r\n\r\n### Sui\r\n\r\nsui[官方示例](https://github.com/MystenLabs/sui/blob/main/sui_programmability/examples/defi/sources/flash_lender.move)中同样实现了闪电贷。\r\n\r\n当用户借款时调用`loan`函数返回一笔资金`coin`和一个记录着借贷金额`value`但没有任何`ability`的`receipt`收据，如果用户试图不归还资金，那么这个收据将被丢弃从而报错，所以必须调用`repay`函数从而销毁收据。收据的销毁完全由模块控制，销毁时验证传入的金额是否等于收据中的金额，从而保证闪电贷的逻辑正确。\r\n\r\n```move\r\nmodule example::flash_lender {\r\n    use sui::balance::{Self, Balance};\r\n    use sui::coin::{Self, Coin};\r\n    use sui::object::{Self, ID, UID};\r\n    use sui::transfer;\r\n    use sui::tx_context::{Self, TxContext};\r\n\r\n    /// A shared object offering flash loans to any buyer willing to pay `fee`.\r\n    struct FlashLender<phantom T> has key {\r\n        id: UID,\r\n        /// Coins available to be lent to prospective borrowers\r\n        to_lend: Balance<T>,\r\n        /// Number of `Coin<T>`'s that will be charged for the loan.\r\n        /// In practice, this would probably be a percentage, but\r\n        /// we use a flat fee here for simplicity.\r\n        fee: u64,\r\n    }\r\n\r\n    /// A \"hot potato\" struct recording the number of `Coin<T>`'s that\r\n    /// were borrowed. Because this struct does not have the `key` or\r\n    /// `store` ability, it cannot be transferred or otherwise placed in\r\n    /// persistent storage. Because it does not have the `drop` ability,\r\n    /// it cannot be discarded. Thus, the only way to get rid of this\r\n    /// struct is to call `repay` sometime during the transaction that created it,\r\n    /// which is exactly what we want from a flash loan.\r\n    struct Receipt<phantom T> {\r\n        /// ID of the flash lender object the debt holder borrowed from\r\n        flash_lender_id: ID,\r\n        /// Total amount of funds the borrower must repay: amount borrowed + the fee\r\n        repay_amount: u64\r\n    }\r\n\r\n    /// An object conveying the privilege to withdraw funds from and deposit funds to the\r\n    /// `FlashLender` instance with ID `flash_lender_id`. Initially granted to the creator\r\n    /// of the `FlashLender`, and only one `AdminCap` per lender exists.\r\n    struct AdminCap has key, store {\r\n        id: UID,\r\n        flash_lender_id: ID,\r\n    }\r\n    \r\n    // === Creating a flash lender ===\r\n\r\n    /// Create a shared `FlashLender` object that makes `to_lend` available for borrowing.\r\n    /// Any borrower will need to repay the borrowed amount and `fee` by the end of the\r\n    /// current transaction.\r\n    public fun new<T>(to_lend: Balance<T>, fee: u64, ctx: &mut TxContext): AdminCap {\r\n        let id = object::new(ctx);\r\n        let flash_lender_id = object::uid_to_inner(&id);\r\n        let flash_lender = FlashLender { id, to_lend, fee };\r\n        // make the `FlashLender` a shared object so anyone can request loans\r\n        transfer::share_object(flash_lender);\r\n\r\n        // give the creator admin permissions\r\n        AdminCap { id: object::new(ctx), flash_lender_id }\r\n    }\r\n\r\n    // === Core functionality: requesting a loan and repaying it ===\r\n\r\n    /// Request a loan of `amount` from `lender`. The returned `Receipt<T>` \"hot potato\" ensures\r\n    /// that the borrower will call `repay(lender, ...)` later on in this tx.\r\n    /// Aborts if `amount` is greater that the amount that `lender` has available for lending.\r\n    public fun loan<T>(\r\n        self: &mut FlashLender<T>, amount: u64, ctx: &mut TxContext\r\n    ): (Coin<T>, Receipt<T>) {\r\n        let to_lend = &mut self.to_lend;\r\n        assert!(balance::value(to_lend) >= amount, ELoanTooLarge);\r\n        let loan = coin::take(to_lend, amount, ctx);\r\n        let repay_amount = amount + self.fee;\r\n        let receipt = Receipt { flash_lender_id: object::id(self), repay_amount };\r\n\r\n        (loan, receipt)\r\n    }\r\n\r\n    /// Repay the loan recorded by `receipt` to `lender` with `payment`.\r\n    /// Aborts if the repayment amount is incorrect or `lender` is not the `FlashLender`\r\n    /// that issued the original loan.\r\n    public fun repay<T>(self: &mut FlashLender<T>, payment: Coin<T>, receipt: Receipt<T>) {\r\n        let Receipt { flash_lender_id, repay_amount } = receipt;\r\n        assert!(object::id(self) == flash_lender_id, ERepayToWrongLender);\r\n        assert!(coin::value(&payment) == repay_amount, EInvalidRepaymentAmount);\r\n\r\n        coin::put(&mut self.to_lend, payment)\r\n    }\r\n}\r\n```\r\n\r\n## 总结\r\n\r\nHot Potato设计模式不仅仅只适用于闪电贷的场景，还可以用来控制更复杂的函数调用顺序。\r\n\r\n例如我们想要一个制作土豆的合约，当用户调用`get_potato`时，会得到一个没有任何能力的`potato`，我们想要用户得倒之后，按照切土豆、煮土豆最后才能吃土豆的一个既定流程来操作。所以用户为了完成交易那么必须最后调用`consume_potato`，但是该函数限制了土豆必须被`cut`和`cook`，所以需要分别调用`cut_potato`和`cook_potato`，`cook_potato`中又限制了必须先被`cut`，从而合约保证了调用顺序必须为get→cut→cook→consume，从而控制了调用顺序。\r\n\r\n```move\r\nmodule example::hot_potato {\r\n    /// Without any capability,\r\n    struct Potato {\r\n        has_cut: bool,\r\n        has_cook: bool,\r\n    }\r\n    /// When calling this function, the `sender` will receive a `Potato` object.\r\n    /// The `sender` can do nothing with the `Potato` such as store, drop,\r\n    /// or move_to the global storage, except passing it to `consume_potato` function.\r\n    public fun get_potato(_sender: &signer): Potato {\r\n        Potato {\r\n            has_cut: false,\r\n            has_cook: false,\r\n        } \r\n    }\r\n\r\n    public fun cut_potatoes(potato: &mut Potato) {\r\n        assert!(!potato.has_cut, 0);\r\n        potato.has_cut = true;\r\n    }\r\n\r\n    public fun cook_potato(potato: &mut Potato) {\r\n        assert!(!potato.has_cook && potato.has_cut, 0);\r\n        potato.has_cook = true;\r\n    }\r\n\r\n    public fun consume_potato(_sender: &signer, potato: Potato) {\r\n        assert!(potato.has_cook && potato.has_cut, 0);\r\n        let Potato {has_cut: _, has_cook: _ } = potato; // destroy the Potato.\r\n    }\r\n}\r\n```"},"author":{"user":"https://learnblockchain.cn/people/6415","address":"0xd69a77bE38f2Fb35De1aCC3AC3626E6d6c8d6928"},"history":null,"timestamp":1668066241,"version":1}