Skip to content

Commit

Permalink
feat: refactor hot potato to exclude ptb
Browse files Browse the repository at this point in the history
  • Loading branch information
daniellam258 committed Mar 11, 2024
1 parent 81b3f53 commit 58dffbf
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 10 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ Introductory course to the Move language maintained by [Sui Foundation](https://
- [Marketplace Contract](./unit-four/lessons/4_marketplace_contract.md)
- [Deployment and Testing](./unit-four/lessons/5_deployment_and_testing.md)
- **Unit Five: Sui Kiosk**
- [Programmable Transaction Block](./unit-five/lessons/1_programmable_transaction_block.md)
- [Hot Potato Design Pattern](./unit-five/lessons/2_hot_potato_pattern.md)
- [Sui Kiosk](./unit-five/lessons/3_kiosk.md)
- [Hot Potato Design Pattern](./unit-five/lessons/1_hot_potato_pattern.md)
- [Sui Kiosk](./unit-five/lessons/2_kiosk.md)
- **Advanced Topics**
- [BCS Encoding](./advanced-topics/BCS_encoding/lessons/BCS_encoding.md)

Expand Down
39 changes: 36 additions & 3 deletions unit-five/example_projects/flashloan/sources/flashloan.move
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module flashloan::flashloan {
use sui::sui::SUI;
use sui::coin::{Self, Coin};
use sui::balance::{Self, Balance};
use sui::object::{UID};
use sui::object::{Self, UID};
use sui::tx_context::{TxContext};

// === Errors ===
Expand All @@ -27,16 +27,22 @@ module flashloan::flashloan {

/// A loan position.
/// This is a hot potato struct, it enforces the users
/// to repay the loan in the end of the transaction or within the same PTB.
/// to repay the loan in the end of the transaction.
struct Loan {
amount: u64,
}

/// Example NFT for demonstration purpose
struct NFT has key, store {
id: UID,
price: Balance<SUI>,
}

// === Public-Mutative Functions ===

/// Function allows users to borrow from the loan pool.
/// It returns the borrowed [`Coin<SUI>`] and the [`Loan`] position
/// enforcing users to fulfill before the PTB ends.
/// enforcing users to fulfill before the transaction ends.
public fun borrow(pool: &mut LoanPool, amount: u64, ctx: &mut TxContext): (Coin<SUI>, Loan) {
assert!(amount <= balance::value(&pool.amount), ELoanAmountExceedPool);

Expand All @@ -56,4 +62,31 @@ module flashloan::flashloan {

balance::join(&mut pool.amount, coin::into_balance(payment));
}

/// Buy a NFT
public fun buy_nft(payment: Coin<SUI>, ctx: &mut TxContext): NFT {
NFT {
id: object::new(ctx),
price: coin::into_balance(payment),
}
}

/// Sell a NFT
public fun sell_nft(nft: NFT, ctx: &mut TxContext): Coin<SUI> {
let NFT {id, price} = nft;
object::delete(id);
coin::from_balance(price, ctx)
}

/// Flashloan
public fun flashloan(pool: &mut LoanPool, amount: u64, ctx: &mut TxContext) {
let (loanCoin, loan) = borrow(pool, amount, ctx);

/// We can call multiple functions in-between `borrow()` and `repay()` to use the loan for our own utility.
/// We demonstrate this behavior by buying a NFT and sell it instantly to repay the debt
let nft = buy_nft(loanCoin, ctx);
let repayCoin = sell_nft(nft, ctx);

repay(pool, loan, repayCoin);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Hot Potato Pattern

A hot potato is a struct that has no capabilities, therefore you can only pack and unpack it in its module. The Hot Potato Pattern leverages the PTB mechanics and is commonly used in cases when the application wants to enforce users to fulfill determined business logic before the transaction ends. In simpler terms, if a hot potato value is returned by the transaction command A, you must consume it in any subsequent command B within the same PTB. The most popular use case of Hot Potato Pattern is flashloan.
A hot potato is a struct that has no capabilities, therefore you can only pack and unpack it in its module. The Hot Potato Pattern is commonly used in cases when the application wants to enforce users to fulfill determined business logic before the transaction ends. It is also usually used in conjunction with Programmable Transaction Block (PTB). The most popular use case of Hot Potato Pattern is flashloan.

*💡Note: Read more details about [Programmable Transaction Block (PTB) here](./programmable_transaction_block.md)*

## Type Definitions

Expand All @@ -27,7 +29,7 @@ module flashloan::flashloan {

/// A loan position.
/// This is a hot potato struct, it enforces the users
/// to repay the loan in the end of the transaction or within the same PTB.
/// to repay the loan in the end of the transaction.
struct Loan {
amount: u64,
}
Expand All @@ -41,7 +43,7 @@ We have a `LoanPool` shared object acting as a money vault ready for users to bo
```rust
/// Function allows users to borrow from the loan pool.
/// It returns the borrowed [`Coin<SUI>`] and the [`Loan`] position
/// enforcing users to fulfill before the PTB ends.
/// enforcing users to fulfill before the transaction ends.
public fun borrow(pool: &mut LoanPool, amount: u64, ctx: &mut TxContext): (Coin<SUI>, Loan) {
assert!(amount <= balance::value(&pool.amount), ELoanAmountExceedPool);

Expand Down Expand Up @@ -69,5 +71,42 @@ public fun repay(pool: &mut LoanPool, loan: Loan, payment: Coin<SUI>) {
}
```

Users at some point must `repay()` the loan before the PTB ends. We consume the `Loan` by unpacking it, otherwise, you will receive compiler error if you use its fields with direct access `loan.amount` as `Loan` is non-`drop`. After unpacking, we simply use the loan amount to perform valid payment check and update the `LoanPool` accordingly.
Users at some point must `repay()` the loan before the transaction ends. We consume the `Loan` by unpacking it, otherwise, you will receive compiler error if you use its fields with direct access `loan.amount` as `Loan` is non-`drop`. After unpacking, we simply use the loan amount to perform valid payment check and update the `LoanPool` accordingly.

## Flashloan

```rust
/// Example NFT for demonstration purpose
struct NFT has key, store {
id: UID,
price: Balance<SUI>,
}

/// Buy a NFT
public fun buy_nft(payment: Coin<SUI>, ctx: &mut TxContext): NFT {
NFT {
id: object::new(ctx),
price: coin::into_balance(payment),
}
}

/// Sell a NFT
public fun sell_nft(nft: NFT, ctx: &mut TxContext): Coin<SUI> {
let NFT {id, price} = nft;
object::delete(id);
coin::from_balance(price, ctx)
}

/// Flashloan
public fun flashloan(pool: &mut LoanPool, amount: u64, ctx: &mut TxContext) {
let (loanCoin, loan) = borrow(pool, amount, ctx);

/// We can call multiple functions in-between `borrow()` and `repay()` to use the loan for our own utility.
/// We demonstrate this behavior by buying a NFT and sell it instantly to repay the debt
let nft = buy_nft(loanCoin, ctx);
let repayCoin = sell_nft(nft, ctx);

repay(pool, loan, repayCoin);
```

`flashloan()` demonstrates how we can borrow the coin and use it for our own utility before repaying the debt all in one single transaction. Between `borrow()` and `repay()`, we can freely execute any logic using the loan we just borrow. In the example, we simply buy a NFT and then sell it for profit, then, the profit is used to repay the loan. In the worst scenario where you incur a loss instead, and you can't payback the loan, then the transaction fails and no state changes to the blockchain are applied. This is a very powerful pattern as it requires you to satisfy some business logic atomically in one single transaction to prevent leaking invalid application states.
File renamed without changes.

0 comments on commit 58dffbf

Please sign in to comment.