Skip to content

Instantly share code, notes, and snippets.

@damirka
Last active August 1, 2022 07:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save damirka/6510d96c17312387a3d6409025258f26 to your computer and use it in GitHub Desktop.
Save damirka/6510d96c17312387a3d6409025258f26 to your computer and use it in GitHub Desktop.
Sui Move samples for the presentation in Korea 01/08
/// 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