Last active
August 1, 2022 07:52
-
-
Save damirka/6510d96c17312387a3d6409025258f26 to your computer and use it in GitHub Desktop.
Sui Move samples for the presentation in Korea 01/08
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Slide #1 | |
/// | |
/// - We show different items that could be represented | |
/// - Introduce types: String, Url, u64, u8, Time? | |
/// | |
/// Language: module, struct | |
module examples::show_me_what_you_got { | |
use sui::url::Url; | |
use sui::object::Info; | |
use sui::utf8::String; | |
// A limited edition NFT? | |
struct Apeach has key { info: Info } | |
// A game character to use in different games? | |
struct Avatar has key { info: Info, name: String, img: Url } | |
// A pair of sneakers? | |
struct BalenciagaTripleS has key { info: Info, edition: u8 } | |
// How about something truly important? 'member Haiti 2010 | |
struct TitleDeed has key { info: Info, issued: u64 } | |
// Imagine all locks are digital now! | |
struct Key has key { info: Info, room: u64 } | |
// And of course the Coin... | |
struct Coin has key { info: Info, value: u64 } | |
} | |
/// Slide #2 | |
/// | |
/// - Show Move expressivity - pass something, get something else | |
/// - Introduce imports, functions, passing something by value | |
/// | |
/// Language: use, struct, function | |
module examples::no_really_why { | |
use sui::tx_context::TxContext; | |
use sui::object::{Self, Info}; | |
use sui::coin::{Self, Coin}; | |
use sui::sui::SUI; | |
/// Not much but something to get by | |
struct Donut has key { info: Info } | |
/// What does it do? | |
fun buy_donut(payment: Coin<SUI>, ctx: &mut TxContext): Donut { | |
assert!(coin::value(&payment) == 1000, 0); | |
coin::transfer(payment, @admin); | |
Donut { info: object::new(ctx) } | |
} | |
} | |
/// Slide #3 | |
/// | |
/// - Demonstration of abilities - Why assets | |
/// - | |
/// | |
/// Language: abilities, wrapping, generics | |
module examples::abilities { | |
use sui::transfer::transfer; | |
use sui::object::{Self, Info}; | |
use sui::tx_context::{TxContext, sender}; | |
struct Droppable has drop { /* ... */ } | |
struct Storable has store, copy { /* ... */ } | |
/// Due to the nature of Sui every type with 'key' must have `info: Info` | |
struct Ownable has key, store { info: Info, data: Storable } | |
/// This guy is generic but only accepts types with `store` and `key` together | |
struct MetaOwner<T: store + key> has key { info: Info, data: T } | |
fun init(ctx: &mut TxContext) { | |
let _ = Droppable {}; // he was never here | |
let storable = Storable {}; | |
// Storable 1 | |
transfer(Ownable { info: object::new(ctx), data: storable }, sender(ctx)); | |
// Storable 2 - cloned itself! | |
let ownable = Ownable { info: object::new(ctx), data: storable }; | |
// Now hols Ownable which has key + store | |
transfer(MetaOwner<Ownable> { info: object::new(ctx), data: ownable }, sender(ctx)); | |
} | |
} | |
/// Slide #4 | |
/// | |
/// - Show how value can be passed | |
/// - Borrow checking, safe methods, mutable methods | |
/// | |
/// Language: import, function, ref, mutref, destroy | |
module examples::borrow_checker_what_now { | |
use sui::tx_context::TxContext; | |
use sui::object::{Self, Info}; | |
use sui::coin::{Self, Coin}; | |
use sui::sui::SUI; | |
/// Multi-use card to use in public transportation | |
struct MetroCard has key { info: Info, uses: u64 } | |
/// Just like buying a donut | |
public fun buy(payment: Coin<SUI>, ctx: &mut TxContext): MetroCard { | |
assert!(coin::value(&payment) == 1000, 0); | |
coin::transfer(payment, @admin); | |
MetroCard { info: object::new(ctx), uses: 10 } | |
} | |
/// Show at the entrance, keep for now | |
public fun enter(card: &mut MetroCard) { | |
assert!(card.uses > 0, 0); | |
card.uses = card.uses - 1; | |
} | |
/// For those who try to cheat on the system | |
public fun ticket_inspection(_: &MetroCard) {} | |
/// Goog guys recycle | |
public fun recycle(card: MetroCard) { | |
let MetroCard { info, uses: _ } = card; | |
object::delete(info); // id can not be dropped, `uses` - can | |
} | |
} | |
/// Section: Main features of the language that | |
/// establish main ways or approaches of using Sui Move. | |
/// Slide #5 | |
/// | |
/// - Module initializer - the place where we usually send something | |
/// - ...or define permissions through capabilities | |
/// | |
/// Language: init function, transfer | |
module examples::another_cool_game { | |
use sui::transfer; | |
use sui::object::{Self, Info}; | |
use sui::tx_context::{Self, TxContext}; | |
/// The one and only | |
struct EldenRing has key { info: Info } | |
/// Once called, we can never go back | |
fun init(ctx: &mut TxContext) { | |
transfer::transfer( | |
EldenRing { info: object::new(ctx) }, | |
tx_context::sender(ctx) | |
) | |
} | |
} | |
/// Slide #6 | |
/// | |
/// - Public getter | |
/// - How NFT module is created | |
/// - Permission management with Capabilities (only admin can call) | |
/// - Sengind Runes to accounts with transfer | |
/// | |
/// Language: imports, init, view fun, entry, capability, transfer | |
module examples::transfer_to_account { | |
use sui::transfer; | |
use sui::object::{Self, Info}; | |
use sui::tx_context::{Self, TxContext}; | |
/// Held only by the dungeon master | |
struct RuneMasterCap has key { info: Info } | |
/// The game currency, you know | |
struct GoldenRune has key, store { info: Info, value: u64 } | |
/// The only way to read the internals | |
public fun value(rune: &GoldenRune): u64 { rune.value } | |
fun init(ctx: &mut TxContext) { | |
transfer::transfer( | |
RuneMasterCap { info: object::new(ctx) }, | |
tx_context::sender(ctx) | |
) | |
} | |
public entry fun mint_and_transfer( | |
_: &RuneMasterCap, size: u64, to: address, ctx: &mut TxContext | |
) { | |
transfer::transfer(GoldenRune { | |
info: object::new(ctx), | |
value: size * 200 | |
}, to) | |
} | |
} | |
/// Slide #7 | |
/// | |
/// - Showcase universal Avatar that can be used in different games | |
/// | |
/// Language: transfer, transfer_to_object | |
module examples::avatar { | |
use sui::transfer; | |
use sui::utf8::{Self, String}; | |
use sui::object::{Self, Info}; | |
use sui::tx_context::{Self, TxContext}; | |
/// Avatar for everything | |
struct Avatar has key { | |
info: Info, | |
name: String, | |
item_count: u64 | |
} | |
/// Even simpler than registering a new game account; | |
public entry fun create(name: vector<u8>, ctx: &mut TxContext) { | |
transfer::transfer(Avatar { | |
info: object::new(ctx), | |
name: utf8::string_unsafe(name), | |
item_count: 0 | |
}, tx_context::sender(ctx)); | |
} | |
/// Put something to hero inventory | |
public entry fun add_item<T: key + store>(a: &mut Avatar, item: T) { | |
transfer::transfer_to_object(item, a); | |
a.item_count = a.item_count + 1; | |
} | |
/// Take item from the hero inventory | |
public fun remove_item<T: key + store>(a: &mut Avatar, item: T): T { | |
a.item_count = a.item_count - 1; | |
item | |
} | |
} | |
/// Slide #8 | |
/// | |
/// - Use Avatar inventory | |
/// - How shared objects work? | |
/// | |
/// Language: import package | |
module examples::arena { | |
use sui::object::{Self, Info}; | |
use sui::balance::{Self, Balance}; | |
use sui::tx_context::{Self, TxContext}; | |
use sui::coin::{Self, Coin}; | |
use sui::transfer; | |
use sui::sui::SUI; | |
// Use the avatar module that we created | |
use examples::avatar::{Self, Avatar}; | |
// Const values for prices | |
const AXE_PRICE: u64 = 100000; | |
const SWORD_PRICE: u64 = 300000; | |
const POTION_PRICE: u64 = 5000; | |
const MAP_PRICE: u64 = 100000; | |
/// For when amount doesn't match the price | |
const EWrongAmount: u64 = 0; | |
// Some armaments | |
struct Axe has key, store { info: Info } | |
struct Sword has key, store { info: Info } | |
// And helpful items | |
struct Potion has key, store { info: Info, strength: u8 } | |
struct Map has key, store { info: Info, area: u8 } | |
/// Only available to the shop owner | |
struct ShopOwnerCap has key, store { info: Info } | |
// Accessible by everyone, feel free to buy items | |
struct ItemShop has key { | |
info: Info, | |
balance: Balance<SUI> | |
} | |
// Create a shop, | |
fun init(ctx: &mut TxContext) { | |
transfer::transfer(ShopOwnerCap { info: object::new(ctx) }, tx_context::sender(ctx)); | |
transfer::share_object(ItemShop { | |
info: object::new(ctx), | |
balance: balance::zero() | |
}); | |
} | |
/// Private (!) function to handle financial side of things. | |
fun purchase(shop: &mut ItemShop, payment: Coin<SUI>, price: u64) { | |
assert!(coin::value(&payment) == price, EWrongAmount); | |
let balance = coin::into_balance(payment); | |
balance::join(&mut shop.balance, balance); | |
} | |
/// Publicly accessible puchase function - Item | |
public entry fun buy_axe( | |
hero: &mut Avatar, shop: &mut ItemShop, payment: Coin<SUI>, ctx: &mut TxContext | |
) { | |
purchase(shop, payment, AXE_PRICE); | |
avatar::add_item(hero, Axe { info: object::new(ctx) }) | |
} | |
/// Publicly accessible puchase function - Sword | |
public entry fun buy_sword( | |
hero: &mut Avatar, shop: &mut ItemShop, payment: Coin<SUI>, ctx: &mut TxContext | |
) { | |
purchase(shop, payment, SWORD_PRICE); | |
avatar::add_item(hero, Sword { info: object::new(ctx) }) | |
} | |
// .... | |
/// Eventually owner can take what he earned by selling armaments and potions | |
public entry fun withdraw_profits( | |
_: &ShopOwnerCap, shop: &mut ItemShop, ctx: &mut TxContext | |
) { | |
let amount = balance::value(&shop.balance); | |
let to_send = balance::split(&mut shop.balance, amount); | |
transfer::transfer( | |
coin::from_balance(to_send, ctx), | |
tx_context::sender(ctx) | |
) | |
} | |
} | |
/// Slide #9 | |
/// | |
/// - Finally a crypto example! | |
/// | |
/// Language: hot potato | |
module examples::flash_lender { | |
use sui::balance::{Self, Balance}; | |
use sui::coin::{Self, Coin}; | |
use sui::object::{Self, ID, Info}; | |
use sui::transfer; | |
use sui::tx_context::{Self, TxContext}; | |
/// A shared object offering flash loans to any buyer willing to pay `fee`. | |
struct FlashLender<phantom T> has key { | |
info: Info, | |
/// Coins available to be lent to prospective borrowers | |
to_lend: Balance<T>, | |
/// Number of `Coin<T>`'s that will be charged for the loan. | |
fee: u64, | |
} | |
/// A "hot potato" struct recording the number of `Coin<T>`'s that | |
/// were borrowed. Because this struct does not have the `key` or | |
/// `store` ability, it cannot be transferred or otherwise placed in | |
/// persistent storage. Because it does not have the `drop` ability, | |
/// it cannot be discarded. Thus, the only way to get rid of this | |
/// struct is to call `repay` sometime during the transaction that created it, | |
/// which is exactly what we want from a flash loan. | |
struct Receipt<phantom T> { | |
/// ID of the flash lender object the debt holder borrowed from | |
flash_lender_id: ID, | |
/// Total amount of funds the borrower must repay: amount borrowed + the fee | |
repay_amount: u64 | |
} | |
/// An object conveying the privilege to withdraw funds from and deposit funds to the | |
/// `FlashLender` instance with ID `flash_lender_id`. Initially granted to the creator | |
/// of the `FlashLender`, and only one `WithdrawCap` per lender exists. | |
struct WithdrawCap has key, store { | |
info: Info, | |
flash_lender_id: ID, | |
} | |
/// Attempted to borrow more than the `FlashLender` has. | |
/// Try borrowing a smaller amount. | |
const ELoanTooLarge: u64 = 0; | |
/// Tried to repay an amount other than `repay_amount` (i.e., the amount borrowed + the fee). | |
/// Try repaying the proper amount. | |
const EInvalidRepaymentAmount: u64 = 1; | |
/// Attempted to repay a `FlashLender` that was not the source of this particular debt. | |
/// Try repaying the correct lender. | |
const ERepayToWrongLender: u64 = 2; | |
/// Attempted to perform an admin-only operation without valid permissions | |
/// Try using the correct `WithdrawCap` | |
const EAdminOnly: u64 = 3; | |
/// Attempted to withdraw more than the `FlashLender` has. | |
/// Try withdrawing a smaller amount. | |
const EWithdrawTooLarge: u64 = 4; | |
// === Creating a flash lender === | |
/// Create a shared `FlashLender` object that makes `to_lend` available for borrowing. | |
/// Any borrower will need to repay the borrowed amount and `fee` by the end of the | |
/// current transaction. | |
public fun new<T>(to_lend: Balance<T>, fee: u64, ctx: &mut TxContext): WithdrawCap { | |
let info = object::new(ctx); | |
let flash_lender_id = *object::info_id(&info); | |
let flash_lender = FlashLender { info, to_lend, fee }; | |
// make the `FlashLender` a shared object so anyone can request loans | |
transfer::share_object(flash_lender); | |
// give the creator admin permissions | |
WithdrawCap { info: object::new(ctx), flash_lender_id } | |
} | |
/// Same as `new`, but transfer `WithdrawCap` to the transaction sender | |
public entry fun create<T>(to_lend: Coin<T>, fee: u64, ctx: &mut TxContext) { | |
let balance = coin::into_balance(to_lend); | |
let withdraw_cap = new(balance, fee, ctx); | |
transfer::transfer(withdraw_cap, tx_context::sender(ctx)) | |
} | |
// === Core functionality: requesting a loan and repaying it === | |
/// Request a loan of `amount` from `lender`. The returned `Receipt<T>` "hot potato" ensures | |
/// that the borrower will call `repay(lender, ...)` later on in this tx. | |
/// Aborts if `amount` is greater that the amount that `lender` has available for lending. | |
public fun loan<T>( | |
self: &mut FlashLender<T>, amount: u64, ctx: &mut TxContext | |
): (Coin<T>, Receipt<T>) { | |
let to_lend = &mut self.to_lend; | |
assert!(balance::value(to_lend) >= amount, ELoanTooLarge); | |
let loan = coin::take(to_lend, amount, ctx); | |
let repay_amount = amount + self.fee; | |
let receipt = Receipt { flash_lender_id: *object::id(self), repay_amount }; | |
(loan, receipt) | |
} | |
/// Repay the loan recorded by `receipt` to `lender` with `payment`. | |
/// Aborts if the repayment amount is incorrect or `lender` is not the `FlashLender` | |
/// that issued the original loan. | |
public fun repay<T>(self: &mut FlashLender<T>, payment: Coin<T>, receipt: Receipt<T>) { | |
let Receipt { flash_lender_id, repay_amount } = receipt; | |
assert!(object::id(self) == &flash_lender_id, ERepayToWrongLender); | |
assert!(coin::value(&payment) == repay_amount, EInvalidRepaymentAmount); | |
coin::put(&mut self.to_lend, payment) | |
} | |
// === Admin-only functionality === | |
/// Allow admin for `self` to withdraw funds. | |
public fun withdraw<T>( | |
self: &mut FlashLender<T>, | |
admin_cap: &WithdrawCap, | |
amount: u64, | |
ctx: &mut TxContext | |
): Coin<T> { | |
// only the holder of the `WithdrawCap` for `self` can withdraw funds | |
check_admin(self, admin_cap); | |
let to_lend = &mut self.to_lend; | |
assert!(balance::value(to_lend) >= amount, EWithdrawTooLarge); | |
coin::take(to_lend, amount, ctx) | |
} | |
/// Allow admin to add more funds to `self` | |
public entry fun deposit<T>( | |
self: &mut FlashLender<T>, admin_cap: &WithdrawCap, coin: Coin<T> | |
) { | |
// only the holder of the `WithdrawCap` for `self` can deposit funds | |
check_admin(self, admin_cap); | |
coin::put(&mut self.to_lend, coin); | |
} | |
/// Allow admin to update the fee for `self` | |
public entry fun update_fee<T>( | |
self: &mut FlashLender<T>, admin_cap: &WithdrawCap, new_fee: u64 | |
) { | |
// only the holder of the `WithdrawCap` for `self` can update the fee | |
check_admin(self, admin_cap); | |
self.fee = new_fee | |
} | |
fun check_admin<T>(self: &FlashLender<T>, admin_cap: &WithdrawCap) { | |
assert!(object::id(self) == &admin_cap.flash_lender_id, EAdminOnly); | |
} | |
// === Reads === | |
/// Return the current fee for `self` | |
public fun fee<T>(self: &FlashLender<T>): u64 { | |
self.fee | |
} | |
/// Return the maximum amount available for borrowing | |
public fun max_loan<T>(self: &FlashLender<T>): u64 { | |
balance::value(&self.to_lend) | |
} | |
/// Return the amount that the holder of `self` must repay | |
public fun repay_amount<T>(self: &Receipt<T>): u64 { | |
self.repay_amount | |
} | |
/// Return the amount that the holder of `self` must repay | |
public fun flash_lender_id<T>(self: &Receipt<T>): ID { | |
self.flash_lender_id | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment