├── Checkin ├── Move.toml ├── readme.md └── sources │ └── module.move ├── FlashLoan ├── Move.toml ├── readme.md └── sources │ └── module.move ├── MoveLock ├── Move.toml ├── readme.md └── sources │ └── move_lock.move ├── README.md └── SimpleGame ├── Move.toml ├── readme.md └── sources ├── adventure.move ├── hero.move ├── inventory.move └── random.move /Checkin/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "checkin" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | movectf = "0x0" 10 | sui = "0000000000000000000000000000000000000002" 11 | -------------------------------------------------------------------------------- /Checkin/readme.md: -------------------------------------------------------------------------------- 1 | # Checkin 2 | This is a simple challenge, follow the steps below to complete the challenge. The goal is calling the get_flag() function to trigger a Flag event, and submit the transaction hash to get the flag. -------------------------------------------------------------------------------- /Checkin/sources/module.move: -------------------------------------------------------------------------------- 1 | module movectf::checkin { 2 | use sui::event; 3 | use sui::tx_context::{Self, TxContext}; 4 | 5 | struct Flag has copy, drop { 6 | user: address, 7 | flag: bool 8 | } 9 | 10 | public entry fun get_flag(ctx: &mut TxContext) { 11 | event::emit(Flag { 12 | user: tx_context::sender(ctx), 13 | flag: true 14 | }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /FlashLoan/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flash" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | movectf = "0x0" 10 | sui = "0000000000000000000000000000000000000002" 11 | -------------------------------------------------------------------------------- /FlashLoan/readme.md: -------------------------------------------------------------------------------- 1 | # FlashLoan 2 | This is a flash loan challenge, do you know what is hot potato? I used it to design a secure flash loan, come and try it. Follow the steps below to complete the challenge. The goal is calling the get_flag() function to trigger a Flag event, and submit the transaction hash to get the flag. 3 | -------------------------------------------------------------------------------- /FlashLoan/sources/module.move: -------------------------------------------------------------------------------- 1 | module movectf::flash{ 2 | 3 | // use sui::object::{Self, UID}; 4 | // use std::vector; 5 | use sui::transfer; 6 | use sui::tx_context::{Self, TxContext}; 7 | use sui::object::{Self, ID, UID}; 8 | use sui::balance::{Self, Balance}; 9 | use sui::coin::{Self, Coin}; 10 | use sui::vec_map::{Self, VecMap}; 11 | use sui::event; 12 | // use std::debug; 13 | 14 | 15 | struct FLASH has drop {} 16 | 17 | struct FlashLender has key { 18 | id: UID, 19 | to_lend: Balance, 20 | last: u64, 21 | lender: VecMap 22 | } 23 | 24 | struct Receipt { 25 | flash_lender_id: ID, 26 | amount: u64 27 | } 28 | 29 | struct AdminCap has key, store { 30 | id: UID, 31 | flash_lender_id: ID, 32 | } 33 | 34 | struct Flag has copy, drop { 35 | user: address, 36 | flag: bool 37 | } 38 | 39 | // creat a FlashLender 40 | public fun create_lend(lend_coin: Coin, ctx: &mut TxContext) { 41 | let to_lend = coin::into_balance(lend_coin); 42 | let id = object::new(ctx); 43 | let lender = vec_map::empty(); 44 | let balance = balance::value(&to_lend); 45 | vec_map::insert(&mut lender ,tx_context::sender(ctx), balance); 46 | let flash_lender = FlashLender { id, to_lend, last: balance, lender}; 47 | transfer::share_object(flash_lender); 48 | } 49 | 50 | // get the loan 51 | public fun loan( 52 | self: &mut FlashLender, amount: u64, ctx: &mut TxContext 53 | ): (Coin, Receipt) { 54 | let to_lend = &mut self.to_lend; 55 | assert!(balance::value(to_lend) >= amount, 0); 56 | let loan = coin::take(to_lend, amount, ctx); 57 | let receipt = Receipt { flash_lender_id: object::id(self), amount }; 58 | 59 | (loan, receipt) 60 | } 61 | 62 | // repay coion to FlashLender 63 | public fun repay(self: &mut FlashLender, payment: Coin) { 64 | coin::put(&mut self.to_lend, payment) 65 | } 66 | 67 | // check the amount in FlashLender is correct 68 | public fun check(self: &mut FlashLender, receipt: Receipt) { 69 | let Receipt { flash_lender_id, amount: _ } = receipt; 70 | assert!(object::id(self) == flash_lender_id, 0); 71 | assert!(balance::value(&self.to_lend) >= self.last, 0); 72 | } 73 | 74 | // init Flash 75 | fun init(witness: FLASH, ctx: &mut TxContext) { 76 | let cap = coin::create_currency(witness, 2, ctx); 77 | let owner = tx_context::sender(ctx); 78 | 79 | let flash_coin = coin::mint(&mut cap, 1000, ctx); 80 | 81 | create_lend(flash_coin, ctx); 82 | transfer::transfer(cap, owner); 83 | } 84 | 85 | // get the balance of FlashLender 86 | public fun balance(self: &mut FlashLender, ctx: &mut TxContext) :u64 { 87 | *vec_map::get(&self.lender, &tx_context::sender(ctx)) 88 | } 89 | 90 | // deposit token to FlashLender 91 | public entry fun deposit( 92 | self: &mut FlashLender, coin: Coin, ctx: &mut TxContext 93 | ) { 94 | let sender = tx_context::sender(ctx); 95 | if (vec_map::contains(&self.lender, &sender)) { 96 | let balance = vec_map::get_mut(&mut self.lender, &sender); 97 | *balance = *balance + coin::value(&coin); 98 | }else { 99 | vec_map::insert(&mut self.lender, sender, coin::value(&coin)); 100 | }; 101 | coin::put(&mut self.to_lend, coin); 102 | } 103 | 104 | // withdraw you token from FlashLender 105 | public entry fun withdraw( 106 | self: &mut FlashLender, 107 | amount: u64, 108 | ctx: &mut TxContext 109 | ){ 110 | let owner = tx_context::sender(ctx); 111 | let balance = vec_map::get_mut(&mut self.lender, &owner); 112 | assert!(*balance >= amount, 0); 113 | *balance = *balance - amount; 114 | 115 | let to_lend = &mut self.to_lend; 116 | transfer::transfer(coin::take(to_lend, amount, ctx), owner); 117 | } 118 | 119 | // check whether you can get the flag 120 | public entry fun get_flag(self: &mut FlashLender, ctx: &mut TxContext) { 121 | if (balance::value(&self.to_lend) == 0) { 122 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /MoveLock/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "move_lock" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | # Sui = { local = "/Users/chris/src/sui/crates/sui-framework/"} 8 | 9 | [addresses] 10 | movectf = "0x0" 11 | sui = "0000000000000000000000000000000000000002" 12 | -------------------------------------------------------------------------------- /MoveLock/readme.md: -------------------------------------------------------------------------------- 1 | # MoveLock 2 | At the end of a beautiful math class the teacher left his students with a problem to solve. Will you be the first one to unlock the secret message? Follow the steps below to complete the challenge. The goal is calling the get_flag() function to trigger a Flag event, and submit the transaction hash to get the flag. -------------------------------------------------------------------------------- /MoveLock/sources/move_lock.move: -------------------------------------------------------------------------------- 1 | module movectf::move_lock { 2 | 3 | // [*] Import dependencies 4 | use std::vector; 5 | 6 | use sui::event; 7 | use sui::object::{Self, UID}; 8 | use sui::transfer; 9 | use sui::tx_context::{Self, TxContext}; 10 | 11 | // [*] Structs 12 | struct ResourceObject has key, store { 13 | id : UID, 14 | balance: u128, 15 | q1: bool 16 | } 17 | 18 | struct Flag has copy, drop { 19 | user: address, 20 | flag: bool 21 | } 22 | 23 | // [*] Module initializer 24 | fun init(ctx: &mut TxContext) { 25 | transfer::share_object(ResourceObject { 26 | id: object::new(ctx), 27 | balance: 100, 28 | q1: false, 29 | }) 30 | } 31 | 32 | // [*] Public functions 33 | public entry fun movectf_unlock(data1 : vector, data2 : vector, resource_object: &mut ResourceObject, _ctx: &mut TxContext) { 34 | 35 | let encrypted_flag : vector = vector[19, 16, 17, 11, 9, 21, 18, 36 | 2, 3, 22, 7, 4, 25, 21, 5, 37 | 7, 23, 6, 23, 5, 13, 3, 5, 38 | 9, 16, 12, 22, 14, 3, 14, 12, 39 | 22, 18, 4, 3, 9, 2, 19, 5, 40 | 16, 7, 20, 1, 11, 18, 23, 4, 41 | 15, 20, 5, 24, 9, 1, 12, 5, 42 | 16, 10, 7, 2, 1, 21, 1, 25, 43 | 18, 22, 2, 2, 7, 25, 15, 7, 10]; 44 | 45 | if (movectf_lock(data1, data2) == encrypted_flag) { 46 | if (!resource_object.q1) { 47 | resource_object.q1 = true; 48 | } 49 | } 50 | 51 | } 52 | 53 | fun movectf_lock(data1 : vector, data2 : vector) : vector { 54 | 55 | let input1 = copy data1; 56 | let plaintext = &mut input1; 57 | let plaintext_length = vector::length(plaintext); 58 | assert!(plaintext_length > 3, 0); 59 | 60 | if ( plaintext_length % 3 != 0) { 61 | if (3 - (plaintext_length % 3) == 2) { 62 | vector::push_back(plaintext, 0); 63 | vector::push_back(plaintext, 0); 64 | plaintext_length = plaintext_length + 2; 65 | } 66 | else { 67 | vector::push_back(plaintext, 0); 68 | plaintext_length = plaintext_length + 1; 69 | } 70 | }; 71 | 72 | let complete_plaintext = vector::empty(); 73 | vector::push_back(&mut complete_plaintext, 4); 74 | vector::push_back(&mut complete_plaintext, 15); 75 | vector::push_back(&mut complete_plaintext, 11); 76 | vector::push_back(&mut complete_plaintext, 0); 77 | vector::push_back(&mut complete_plaintext, 13); 78 | vector::push_back(&mut complete_plaintext, 4); 79 | vector::push_back(&mut complete_plaintext, 19); 80 | vector::push_back(&mut complete_plaintext, 19); 81 | vector::push_back(&mut complete_plaintext, 19); 82 | vector::append(&mut complete_plaintext, *plaintext); 83 | plaintext_length = plaintext_length + 9; 84 | 85 | let input2 = copy data2; 86 | let key = &mut input2; 87 | let a11 = *vector::borrow(key, 0); 88 | let a12 = *vector::borrow(key, 1); 89 | let a13 = *vector::borrow(key, 2); 90 | let a21 = *vector::borrow(key, 3); 91 | let a22 = *vector::borrow(key, 4); 92 | let a23 = *vector::borrow(key, 5); 93 | let a31 = *vector::borrow(key, 6); 94 | let a32 = *vector::borrow(key, 7); 95 | let a33 = *vector::borrow(key, 8); 96 | 97 | assert!(vector::length(key) == 9, 0); 98 | 99 | let i : u64 = 0; 100 | let ciphertext = vector::empty(); 101 | while (i < plaintext_length) { 102 | let p11 = *vector::borrow(&mut complete_plaintext, i+0); 103 | let p21 = *vector::borrow(&mut complete_plaintext, i+1); 104 | let p31 = *vector::borrow(&mut complete_plaintext, i+2); 105 | 106 | let c11 = ( (a11 * p11) + (a12 * p21) + (a13 * p31) ) % 26; 107 | let c21 = ( (a21 * p11) + (a22 * p21) + (a23 * p31) ) % 26; 108 | let c31 = ( (a31 * p11) + (a32 * p21) + (a33 * p31) ) % 26; 109 | 110 | vector::push_back(&mut ciphertext, c11); 111 | vector::push_back(&mut ciphertext, c21); 112 | vector::push_back(&mut ciphertext, c31); 113 | 114 | i = i + 3; 115 | }; 116 | 117 | ciphertext 118 | 119 | } 120 | 121 | public entry fun get_flag(resource_object: &ResourceObject, ctx: &mut TxContext) { 122 | if (resource_object.q1) { 123 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MoveCTF-1st-Challenge -------------------------------------------------------------------------------- /SimpleGame/Move.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctf" 3 | version = "0.0.1" 4 | 5 | [dependencies] 6 | Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework", rev = "devnet" } 7 | 8 | [addresses] 9 | ctf = "0x0" 10 | game = "0x0" 11 | sui = "0x2" 12 | -------------------------------------------------------------------------------- /SimpleGame/readme.md: -------------------------------------------------------------------------------- 1 | # SimpleGame 2 | This is a simple game challenge, try to beat the boss and get treasury box, and open box to get the flag. Follow the steps below to complete the challenge. The goal is calling the get_flag() function to trigger a Flag event, and submit the transaction hash to get the flag. -------------------------------------------------------------------------------- /SimpleGame/sources/adventure.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Example of a game character with basic attributes, inventory, and 4 | /// associated logic. 5 | module game::adventure { 6 | use game::inventory; 7 | use game::hero::{Self, Hero}; 8 | use ctf::random; 9 | 10 | use sui::event; 11 | use sui::object::{Self, ID, UID}; 12 | use sui::transfer; 13 | use sui::tx_context::{Self, TxContext}; 14 | 15 | /// A creature that the hero can slay to level up 16 | struct Monster has key { 17 | id: UID, 18 | /// Hit points before the boar is slain 19 | hp: u64, 20 | /// Strength of this particular boar 21 | strength: u64, 22 | /// Defense of this particular boar 23 | defense: u64, 24 | } 25 | 26 | struct Boar {} 27 | struct BoarKing {} 28 | 29 | /// Event emitted each time a Hero slays a Boar 30 | struct SlainEvent has copy, drop { 31 | /// Address of the user that slayed the boar 32 | slayer_address: address, 33 | /// ID of the Hero that slayed the boar 34 | hero: ID, 35 | /// ID of the now-deceased boar 36 | boar: ID, 37 | } 38 | 39 | // TODO: proper error codes 40 | /// The hero is too tired to fight 41 | const EHERO_TIRED: u64 = 1; 42 | /// Trying to remove a sword, but the hero does not have one 43 | const ENO_SWORD: u64 = 4; 44 | const ENO_ARMOR: u64 = 5; 45 | 46 | 47 | /// Boar attributes values 48 | const BOAR_MIN_HP: u64 = 80; 49 | const BOAR_MAX_HP: u64 = 120; 50 | const BOAR_MIN_STRENGTH: u64 = 5; 51 | const BOAR_MAX_STRENGTH: u64 = 15; 52 | const BOAR_MIN_DEFENSE: u64 = 4; 53 | const BOAR_MAX_DEFENSE: u64 = 6; 54 | 55 | /// BoarKing attributes values 56 | const BOARKING_MIN_HP: u64 = 180; 57 | const BOARKING_MAX_HP: u64 = 220; 58 | const BOARKING_MIN_STRENGTH: u64 = 20; 59 | const BOARKING_MAX_STRENGTH: u64 = 25; 60 | const BOARKING_MIN_DEFENSE: u64 = 10; 61 | const BOARKING_MAX_DEFENSE: u64 = 15; 62 | 63 | fun create_monster( 64 | min_hp: u64, max_hp: u64, 65 | min_strength: u64, max_strength: u64, 66 | min_defense: u64, max_defense: u64, 67 | ctx: &mut TxContext 68 | ): Monster { 69 | let id = object::new(ctx); 70 | let hp = random::rand_u64_range(min_hp, max_hp, ctx); 71 | let strength = random::rand_u64_range(min_strength, max_strength, ctx); 72 | let defense = random::rand_u64_range(min_defense, max_defense, ctx); 73 | Monster { 74 | id, 75 | hp, 76 | strength, 77 | defense, 78 | } 79 | } 80 | 81 | // --- Gameplay --- 82 | 83 | /// Fight with the monster 84 | /// return: 0: tie, 1: hero win, 2: monster win; 85 | fun fight_monster(hero: &Hero, monster: &Monster): u64 { 86 | let hero_strength = hero::strength(hero); 87 | let hero_defense = hero::defense(hero); 88 | let hero_hp = hero::hp(hero); 89 | let monster_hp = monster.hp; 90 | // attack the monster until its HP goes to zero 91 | let cnt = 0u64; // max fight times 92 | let rst = 0u64; // 0: tie, 1: hero win, 2: monster win; 93 | while (monster_hp > 0) { 94 | // first, the hero attacks 95 | let damage = if (hero_strength > monster.defense) { 96 | hero_strength - monster.defense 97 | } else { 98 | 0 99 | }; 100 | if (damage < monster_hp) { 101 | monster_hp = monster_hp - damage; 102 | // then, the boar gets a turn to attack. if the boar would kill 103 | // the hero, abort--we can't let the boar win! 104 | let damage = if (monster.strength > hero_defense) { 105 | monster.strength - hero_defense 106 | } else { 107 | 0 108 | }; 109 | if (damage >= hero_hp) { 110 | rst = 2; 111 | break 112 | } else { 113 | hero_hp = hero_hp - damage; 114 | } 115 | } else { 116 | rst = 1; 117 | break 118 | }; 119 | cnt = cnt + 1; 120 | if (cnt > 20) { 121 | break 122 | } 123 | }; 124 | rst 125 | } 126 | 127 | public entry fun slay_boar(hero: &mut Hero, ctx: &mut TxContext) { 128 | assert!(hero::stamina(hero) > 0, EHERO_TIRED); 129 | let boar = create_monster( 130 | BOAR_MIN_HP, BOAR_MAX_HP, 131 | BOAR_MIN_STRENGTH, BOAR_MAX_STRENGTH, 132 | BOAR_MIN_DEFENSE, BOAR_MAX_DEFENSE, 133 | ctx 134 | ); 135 | let fight_result = fight_monster(hero, &boar); 136 | hero::decrease_stamina(hero, 1); 137 | // hero takes their licks 138 | if (fight_result == 1) { 139 | hero::increase_experience(hero, 1); 140 | 141 | let d100 = random::rand_u64_range(0, 100, ctx); 142 | if (d100 < 10) { 143 | let sword = inventory::create_sword(ctx); 144 | hero::equip_or_levelup_sword(hero, sword, ctx); 145 | } else if (d100 < 20) { 146 | let armor = inventory::create_armor(ctx); 147 | hero::equip_or_levelup_armor(hero, armor, ctx); 148 | }; 149 | }; 150 | // let the world know about the hero's triumph by emitting an event! 151 | event::emit(SlainEvent { 152 | slayer_address: tx_context::sender(ctx), 153 | hero: hero::id(hero), 154 | boar: object::uid_to_inner(&boar.id), 155 | }); 156 | let Monster { id, hp: _, strength: _, defense: _} = boar; 157 | object::delete(id); 158 | } 159 | 160 | public entry fun slay_boar_king(hero: &mut Hero, ctx: &mut TxContext) { 161 | assert!(hero::stamina(hero) > 0, EHERO_TIRED); 162 | let boar = create_monster( 163 | BOARKING_MIN_HP, BOARKING_MAX_HP, 164 | BOARKING_MIN_STRENGTH, BOARKING_MAX_STRENGTH, 165 | BOARKING_MIN_DEFENSE, BOARKING_MAX_DEFENSE, 166 | ctx 167 | ); 168 | let fight_result = fight_monster(hero, &boar); 169 | hero::decrease_stamina(hero, 2); 170 | // hero takes their licks 171 | if (fight_result == 1) { // hero won 172 | hero::increase_experience(hero, 2); 173 | 174 | let d100 = random::rand_u64_range(0, 100, ctx); 175 | if (d100 == 0) { 176 | let box = inventory::create_treasury_box(ctx); 177 | transfer::transfer(box, tx_context::sender(ctx)); 178 | }; 179 | }; 180 | // let the world know about the hero's triumph by emitting an event! 181 | event::emit(SlainEvent { 182 | slayer_address: tx_context::sender(ctx), 183 | hero: hero::id(hero), 184 | boar: object::uid_to_inner(&boar.id), 185 | }); 186 | let Monster { id, hp: _, strength: _, defense: _} = boar; 187 | object::delete(id); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /SimpleGame/sources/hero.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Example of a game character with basic attributes, inventory, and 4 | /// associated logic. 5 | module game::hero { 6 | use game::inventory::{Self, Sword, Armor}; 7 | 8 | use sui::object::{Self, ID, UID}; 9 | use sui::transfer; 10 | use sui::tx_context::TxContext; 11 | use std::option::{Self, Option}; 12 | 13 | friend game::adventure; 14 | 15 | 16 | /// Our hero! 17 | struct Hero has key, store { 18 | id: UID, 19 | /// Level of the hero. 20 | level: u64, 21 | /// Stamina points, once exhausted, the hero can't fight. 22 | stamina: u64, 23 | /// Hit points. If they go to zero, the hero can't do anything 24 | hp: u64, 25 | /// Experience of the hero. Begins at zero 26 | experience: u64, 27 | /// Level of the hero. High level will have more high attributes 28 | 29 | /// Attributes of the hero. Increasing with level and weapons. 30 | strength: u64, 31 | defense: u64, 32 | 33 | /// The hero's inventory 34 | sword: Option, 35 | armor: Option, 36 | } 37 | 38 | const MAX_LEVEL: u64 = 2; 39 | const INITAL_HERO_HP: u64 = 100; 40 | const INITIAL_HERO_STRENGTH: u64 = 10; 41 | const INITIAL_HERO_DEFENSE: u64 = 5; 42 | const HERO_STAMINA: u64 = 200; 43 | 44 | // TODO: proper error codes 45 | /// The boar won the battle 46 | const EBOAR_WON: u64 = 0; 47 | /// The hero is too tired to fight 48 | const EHERO_TIRED: u64 = 1; 49 | /// Trying to initialize from a non-admin account 50 | const ENOT_ADMIN: u64 = 2; 51 | /// Not enough money to purchase the given item 52 | const EINSUFFICIENT_FUNDS: u64 = 3; 53 | /// Trying to remove a sword, but the hero does not have one 54 | const ENO_SWORD: u64 = 4; 55 | const ENO_ARMOR: u64 = 5; 56 | /// Assertion errors for testing 57 | const ASSERT_ERR: u64 = 6; 58 | const EHERO_REACH_MAX_LEVEL: u64 = 7; 59 | 60 | // --- Object creation --- 61 | fun init(ctx: &mut TxContext) { 62 | let hero = create_hero(ctx); 63 | transfer::share_object(hero); 64 | } 65 | 66 | /// Create a hero. 67 | public(friend) fun create_hero(ctx: &mut TxContext): Hero { 68 | Hero { 69 | id: object::new(ctx), 70 | level: 1, 71 | stamina: HERO_STAMINA, 72 | hp: INITAL_HERO_HP, 73 | experience: 0, 74 | strength: INITIAL_HERO_STRENGTH, 75 | defense: INITIAL_HERO_DEFENSE, 76 | sword: option::none(), 77 | armor: option::none(), 78 | } 79 | } 80 | 81 | /// Strength of the hero when attacking 82 | public fun strength(hero: &Hero): u64 { 83 | // a hero with zero HP is too tired to fight 84 | if (hero.hp == 0) { 85 | return 0 86 | }; 87 | 88 | let sword_strength = if (option::is_some(&hero.sword)) { 89 | inventory::strength(option::borrow(&hero.sword)) 90 | } else { 91 | // hero can fight without a sword, but will not be very strong 92 | 0 93 | }; 94 | hero.strength + sword_strength 95 | } 96 | 97 | /// Defense of the hero when attacking 98 | public fun defense(hero: &Hero): u64 { 99 | // a hero with zero HP is too tired to fight 100 | if (hero.hp == 0) { 101 | return 0 102 | }; 103 | 104 | let armor_defense = if (option::is_some(&hero.armor)) { 105 | inventory::defense(option::borrow(&hero.armor)) 106 | } else { 107 | // hero can fight without a sword, but will not be very strong 108 | 0 109 | }; 110 | hero.defense + armor_defense 111 | } 112 | 113 | public fun hp(hero: &Hero): u64 { 114 | hero.hp 115 | } 116 | 117 | public fun experience(hero: &Hero): u64 { 118 | hero.experience 119 | } 120 | 121 | public fun stamina(hero: &Hero): u64 { 122 | hero.stamina 123 | } 124 | 125 | public(friend) fun increase_experience(hero: &mut Hero, experience: u64) { 126 | hero.experience = hero.experience + experience; 127 | } 128 | 129 | public(friend) fun id(hero: &Hero): ID { 130 | object::uid_to_inner(&hero.id) 131 | } 132 | 133 | public(friend) fun decrease_stamina(hero: &mut Hero, stamina: u64) { 134 | hero.stamina = hero.stamina - stamina; 135 | } 136 | 137 | public entry fun level_up(hero: &mut Hero) { 138 | assert!(hero.level < MAX_LEVEL, EHERO_REACH_MAX_LEVEL); 139 | if (hero.experience >= 100) { 140 | hero.level = hero.level + 1; 141 | hero.strength = hero.strength + INITIAL_HERO_STRENGTH; 142 | hero.defense = hero.defense + INITIAL_HERO_DEFENSE; 143 | hero.hp = hero.hp + INITAL_HERO_HP; 144 | hero.experience = hero.experience - 100; 145 | } 146 | } 147 | // --- Equipments --- 148 | /// Add `new_sword` to the hero's inventory and return the old sword 149 | /// (if any) 150 | public fun equip_or_levelup_sword(hero: &mut Hero, new_sword: Sword, ctx: &mut TxContext) { 151 | let sword = if (option::is_some(&hero.sword)) { 152 | let sword = option::extract(&mut hero.sword); 153 | inventory::level_up_sword(&mut sword, new_sword, ctx); 154 | sword 155 | } else { 156 | new_sword 157 | }; 158 | option::fill(&mut hero.sword, sword); 159 | } 160 | 161 | /// Disarm the hero by returning their sword. 162 | /// Aborts if the hero does not have a sword. 163 | public fun remove_sword(hero: &mut Hero): Sword { 164 | assert!(option::is_some(&hero.sword), ENO_SWORD); 165 | option::extract(&mut hero.sword) 166 | } 167 | 168 | /// Add `new_sword` to the hero's inventory and return the old sword 169 | /// (if any) 170 | public fun equip_or_levelup_armor(hero: &mut Hero, new_armor: Armor, ctx: &mut TxContext) { 171 | let armor = if (option::is_some(&hero.armor)) { 172 | let armor = option::extract(&mut hero.armor); 173 | inventory::level_up_armor(&mut armor, new_armor, ctx); 174 | armor 175 | } else { 176 | new_armor 177 | }; 178 | option::fill(&mut hero.armor, armor); 179 | } 180 | 181 | /// Disarm the hero by returning their armor. 182 | /// Aborts if the hero does not have a armor. 183 | public fun remove_armor(hero: &mut Hero): Armor { 184 | assert!(option::is_some(&hero.armor), ENO_ARMOR); 185 | option::extract(&mut hero.armor) 186 | } 187 | 188 | public fun destroy_hero(hero: Hero) { 189 | let Hero {id, level: _, stamina: _, hp: _, experience: _, strength: _, defense: _, sword, armor} = hero; 190 | object::delete(id); 191 | if (option::is_some(&sword)) { 192 | let sword = option::destroy_some(sword); 193 | inventory::destroy_sword(sword); 194 | } else { 195 | option::destroy_none(sword); 196 | }; 197 | if (option::is_some(&armor)) { 198 | let armor = option::destroy_some(armor); 199 | inventory::destroy_armor(armor); 200 | } else { 201 | option::destroy_none(armor); 202 | }; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /SimpleGame/sources/inventory.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | 3 | /// Equipments of hero 4 | module game::inventory { 5 | use ctf::random; 6 | 7 | use sui::object::{Self, UID}; 8 | use sui::tx_context::{Self, TxContext}; 9 | use sui::event; 10 | 11 | friend game::adventure; 12 | 13 | /// Upper bound on how magical a sword can be 14 | const MAX_RARITY: u64 = 5; 15 | const BASE_SWORD_STRENGTH: u64 = 2; 16 | const BASE_ARMOR_DEFENSE: u64 = 1; 17 | 18 | /// The hero's trusty sword 19 | struct Sword has store { 20 | /// Constant set at creation. Acts as a multiplier on sword's strength. 21 | /// Swords with high rarity are rarer, rarity ranges between [1, 5] 22 | rarity: u64, 23 | /// Strength that will be add to hero once equipped. 24 | strength: u64, 25 | } 26 | 27 | /// Armor 28 | struct Armor has store { 29 | /// Constant set at creation. Acts as a multiplier on armor's defense. 30 | /// Armors with high rarity are rarer, rarity ranges between [1, 5] 31 | rarity: u64, 32 | /// Defense that will be add to hero once euipped. 33 | defense: u64, 34 | } 35 | 36 | struct TreasuryBox has key, store { 37 | id: UID, 38 | } 39 | 40 | struct Flag has copy, drop { 41 | user: address, 42 | flag: bool 43 | } 44 | 45 | public(friend) fun create_treasury_box(ctx: &mut TxContext): TreasuryBox { 46 | TreasuryBox { 47 | id: object::new(ctx) 48 | } 49 | } 50 | 51 | /// Create a new sword, the rarity value is random generated. 52 | public(friend) fun create_sword(_ctx: &mut TxContext): Sword { 53 | Sword { 54 | rarity: 1, 55 | strength: BASE_SWORD_STRENGTH, 56 | } 57 | } 58 | 59 | public fun destroy_sword(sword: Sword) { 60 | let Sword { rarity: _, strength: _} = sword; 61 | } 62 | 63 | /// Create a new armor, the rarity value is random generated. 64 | public(friend) fun create_armor(_ctx: &mut TxContext): Armor { 65 | Armor { 66 | rarity: 1, 67 | defense: BASE_ARMOR_DEFENSE, 68 | } 69 | } 70 | 71 | public fun destroy_armor(armor: Armor) { 72 | let Armor { rarity: _, defense: _} = armor; 73 | } 74 | 75 | /// Get the strength of a sword: rarity * strength 76 | public fun strength(sword: &Sword): u64 { 77 | sword.strength * sword.rarity 78 | } 79 | 80 | /// Get the defense of an armor: rarity * defense 81 | public fun defense(armor: &Armor): u64 { 82 | armor.defense * armor.rarity 83 | } 84 | 85 | public fun sword_rarity(sword: &Sword): u64 { 86 | sword.rarity 87 | } 88 | 89 | public fun armor_rarity(armor: &Armor): u64 { 90 | armor.rarity 91 | } 92 | 93 | /// Level up a sword with another one. 94 | /// The probability of success is: 1 / sword.rarity. 95 | public fun level_up_sword(sword: &mut Sword, material: Sword, ctx: &mut TxContext) { 96 | if (sword.rarity < MAX_RARITY) { 97 | let prob = random::rand_u64_range(0, sword.rarity, ctx); 98 | if (prob < 1) { 99 | sword.rarity = sword.rarity + 1; 100 | } 101 | }; 102 | destroy_sword(material); 103 | } 104 | 105 | /// Level up an armor with another one. 106 | /// The probability of success is: 1 / armor.rarity. 107 | public fun level_up_armor(armor: &mut Armor, material: Armor, ctx: &mut TxContext) { 108 | if (armor.rarity < MAX_RARITY) { 109 | let prob = random::rand_u64_range(0, armor.rarity, ctx); 110 | if (prob < 1) { 111 | armor.rarity = armor.rarity + 1; 112 | } 113 | }; 114 | destroy_armor(material); 115 | } 116 | 117 | public entry fun get_flag(box: TreasuryBox, ctx: &mut TxContext) { 118 | let TreasuryBox { id } = box; 119 | object::delete(id); 120 | let d100 = random::rand_u64_range(0, 100, ctx); 121 | if (d100 == 0) { 122 | event::emit(Flag { user: tx_context::sender(ctx), flag: true }); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /SimpleGame/sources/random.move: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Based on: https://github.com/starcoinorg/starcoin-framework-commons/blob/main/sources/PseudoRandom.move 3 | 4 | /// @title pseudorandom 5 | /// @notice A pseudo random module on-chain. 6 | /// @dev Warning: 7 | /// The random mechanism in smart contracts is different from 8 | /// that in traditional programming languages. The value generated 9 | /// by random is predictable to Miners, so it can only be used in 10 | /// simple scenarios where Miners have no incentive to cheat. If 11 | /// large amounts of money are involved, DO NOT USE THIS MODULE to 12 | /// generate random numbers; try a more secure way. 13 | module ctf::random { 14 | use std::hash; 15 | use std::vector; 16 | 17 | use sui::bcs; 18 | use sui::object; 19 | use sui::tx_context::TxContext; 20 | 21 | const ERR_HIGH_ARG_GREATER_THAN_LOW_ARG: u64 = 101; 22 | 23 | fun seed(ctx: &mut TxContext): vector { 24 | let ctx_bytes = bcs::to_bytes(ctx); 25 | let uid = object::new(ctx); 26 | let uid_bytes: vector = object::uid_to_bytes(&uid); 27 | object::delete(uid); 28 | 29 | let info: vector = vector::empty(); 30 | vector::append(&mut info, ctx_bytes); 31 | vector::append(&mut info, uid_bytes); 32 | 33 | let hash: vector = hash::sha3_256(info); 34 | hash 35 | } 36 | 37 | fun bytes_to_u64(bytes: vector): u64 { 38 | let value = 0u64; 39 | let i = 0u64; 40 | while (i < 8) { 41 | value = value | ((*vector::borrow(&bytes, i) as u64) << ((8 * (7 - i)) as u8)); 42 | i = i + 1; 43 | }; 44 | return value 45 | } 46 | 47 | /// Generate a random u64 48 | fun rand_u64_with_seed(_seed: vector): u64 { 49 | bytes_to_u64(_seed) 50 | } 51 | 52 | /// Generate a random integer range in [low, high). 53 | fun rand_u64_range_with_seed(_seed: vector, low: u64, high: u64): u64 { 54 | assert!(high > low, ERR_HIGH_ARG_GREATER_THAN_LOW_ARG); 55 | let value = rand_u64_with_seed(_seed); 56 | (value % (high - low)) + low 57 | } 58 | 59 | /// Generate a random u64 60 | public fun rand_u64(ctx: &mut TxContext): u64 { 61 | rand_u64_with_seed(seed(ctx)) 62 | } 63 | 64 | /// Generate a random integer range in [low, high). 65 | public fun rand_u64_range(low: u64, high: u64, ctx: &mut TxContext): u64 { 66 | rand_u64_range_with_seed(seed(ctx), low, high) 67 | } 68 | } 69 | --------------------------------------------------------------------------------