From 2f71d9d79b78627901cafe51dfaffbecafab3dce Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Fri, 12 Jul 2024 22:20:13 +0100 Subject: [PATCH] [Examples/Move] Delete `sui_programmability` (#18612) ## Description Remove the `sui_programmability` folder as all examples have been ported and modernised to `examples/move`, or elsewhere. ## Test plan CI ## Stack - #18525 - #18526 - #18557 - #18558 - #18595 - #18609 --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --------- Co-authored-by: Ronny Roland --- sui_programmability/examples/README.md | 15 +- sui_programmability/examples/basics/Move.toml | 10 - sui_programmability/examples/basics/README.md | 7 - .../examples/basics/sources/clock.move | 17 - .../examples/basics/sources/counter.move | 119 ---- .../examples/basics/sources/lock.move | 134 ---- .../examples/basics/sources/object.move | 105 ---- .../basics/sources/object_basics.move | 62 -- .../examples/basics/sources/random.move | 18 - .../examples/basics/sources/sandwich.move | 172 ------ sui_programmability/examples/capy/Move.toml | 11 - .../examples/capy/sources/capy.move | 486 --------------- .../examples/capy/sources/capy_admin.move | 31 - .../examples/capy/sources/capy_item.move | 174 ------ .../examples/capy/sources/capy_market.move | 269 -------- .../examples/capy/sources/capy_winter.move | 227 ------- .../examples/capy/sources/eden.move | 60 -- sui_programmability/examples/crypto/Move.toml | 10 - sui_programmability/examples/crypto/README.md | 8 - .../examples/crypto/sources/ec_ops.move | 439 ------------- .../examples/crypto/sources/ecdsa.move | 95 --- .../examples/crypto/sources/groth16.move | 117 ---- sui_programmability/examples/defi/Move.toml | 10 - sui_programmability/examples/defi/README.md | 5 - .../examples/defi/sources/escrow.move | 91 --- .../examples/defi/sources/flash_lender.move | 175 ------ .../examples/defi/sources/pool.move | 579 ------------------ .../examples/defi/sources/shared_escrow.move | 70 --- .../examples/defi/sources/subscription.move | 164 ----- .../examples/defi/tests/escrow_tests.move | 167 ----- .../defi/tests/flash_lender_tests.move | 64 -- .../defi/tests/shared_escrow_test.move | 186 ------ .../examples/ecommerce/Move.toml | 10 - .../examples/ecommerce/README.md | 9 - .../examples/ecommerce/sources/.gitkeep | 0 .../examples/ecommerce/tests/.gitkeep | 0 .../examples/fungible_tokens/Move.toml | 12 - .../examples/fungible_tokens/README.md | 9 - .../fungible_tokens/sources/basket.move | 95 --- .../fungible_tokens/sources/managed.move | 42 -- .../sources/regulated_coin.move | 491 --------------- .../sources/treasury_lock.move | 441 ------------- .../fungible_tokens/tests/basket_tests.move | 48 -- sui_programmability/examples/games/Move.toml | 10 - sui_programmability/examples/games/README.md | 12 - .../games/sources/drand_based_lottery.move | 157 ----- .../sources/drand_based_scratch_card.move | 186 ------ .../examples/games/sources/drand_lib.move | 81 --- .../examples/games/sources/hero.move | 401 ------------ .../games/sources/rock_paper_scissors.move | 253 -------- .../examples/games/sources/sea_hero.move | 116 ---- .../games/sources/sea_hero_helper.move | 95 --- .../games/sources/shared_tic_tac_toe.move | 168 ----- .../examples/games/sources/tic_tac_toe.move | 288 --------- .../games/sources/vdf_based_lottery.move | 160 ----- .../tests/drand_based_lottery_tests.move | 98 --- .../tests/drand_based_scratch_card_tests.move | 110 ---- .../tests/rock_paper_scissors_tests.move | 109 ---- .../games/tests/shared_tic_tac_toe_tests.move | 223 ------- .../games/tests/tic_tac_toe_tests.move | 236 ------- .../games/tests/vdf_based_lottery_tests.move | 173 ------ .../examples/move_tutorial/Move.toml | 10 - .../move_tutorial/sources/my_module.move | 168 ----- .../examples/multi_package/README.md | 5 - .../multi_package/dep_package/Move.toml | 10 - .../dep_package/sources/dep_module.move | 10 - .../multi_package/main_package/Move.toml | 11 - .../main_package/sources/main_module.move | 12 - sui_programmability/examples/nfts/Move.toml | 10 - sui_programmability/examples/nfts/README.md | 8 - .../examples/nfts/sources/auction.move | 109 ---- .../examples/nfts/sources/auction_lib.move | 159 ----- .../examples/nfts/sources/chat.move | 82 --- .../nfts/sources/cross_chain_airdrop.move | 185 ------ .../examples/nfts/sources/devnet_nft.move | 122 ---- .../nfts/sources/discount_coupon.move | 62 -- .../examples/nfts/sources/geniteam.move | 377 ------------ .../examples/nfts/sources/marketplace.move | 346 ----------- .../examples/nfts/sources/num.move | 57 -- .../examples/nfts/sources/shared_auction.move | 75 --- .../examples/nfts/tests/auction_tests.move | 118 ---- .../examples/nfts/tests/chat_tests.move | 36 -- .../nfts/tests/cross_chain_airdrop_tests.move | 82 --- .../nfts/tests/discount_coupon_tests.move | 54 -- .../nfts/tests/shared_auction_tests.move | 126 ---- .../examples/objects_tutorial/Move.toml | 11 - .../sources/color_object.move | 227 ------- .../sources/simple_warrior.move | 61 -- .../sources/trusted_swap.move | 81 --- sui_programmability/examples/utils/Move.toml | 10 - .../utils/sources/epoch_time_lock.move | 34 - .../sources/immutable_external_resource.move | 47 -- .../examples/utils/sources/locked_coin.move | 51 -- .../examples/utils/sources/safe.move | 167 ----- .../examples/utils/sources/typed_id.move | 45 -- .../immutable_external_resource_tests.move | 34 - .../examples/utils/tests/safe_tests.move | 174 ------ 97 files changed, 1 insertion(+), 11305 deletions(-) delete mode 100644 sui_programmability/examples/basics/Move.toml delete mode 100644 sui_programmability/examples/basics/README.md delete mode 100644 sui_programmability/examples/basics/sources/clock.move delete mode 100644 sui_programmability/examples/basics/sources/counter.move delete mode 100644 sui_programmability/examples/basics/sources/lock.move delete mode 100644 sui_programmability/examples/basics/sources/object.move delete mode 100644 sui_programmability/examples/basics/sources/object_basics.move delete mode 100644 sui_programmability/examples/basics/sources/random.move delete mode 100644 sui_programmability/examples/basics/sources/sandwich.move delete mode 100644 sui_programmability/examples/capy/Move.toml delete mode 100644 sui_programmability/examples/capy/sources/capy.move delete mode 100644 sui_programmability/examples/capy/sources/capy_admin.move delete mode 100644 sui_programmability/examples/capy/sources/capy_item.move delete mode 100644 sui_programmability/examples/capy/sources/capy_market.move delete mode 100644 sui_programmability/examples/capy/sources/capy_winter.move delete mode 100644 sui_programmability/examples/capy/sources/eden.move delete mode 100644 sui_programmability/examples/crypto/Move.toml delete mode 100644 sui_programmability/examples/crypto/README.md delete mode 100644 sui_programmability/examples/crypto/sources/ec_ops.move delete mode 100644 sui_programmability/examples/crypto/sources/ecdsa.move delete mode 100644 sui_programmability/examples/crypto/sources/groth16.move delete mode 100644 sui_programmability/examples/defi/Move.toml delete mode 100644 sui_programmability/examples/defi/README.md delete mode 100644 sui_programmability/examples/defi/sources/escrow.move delete mode 100644 sui_programmability/examples/defi/sources/flash_lender.move delete mode 100644 sui_programmability/examples/defi/sources/pool.move delete mode 100644 sui_programmability/examples/defi/sources/shared_escrow.move delete mode 100644 sui_programmability/examples/defi/sources/subscription.move delete mode 100644 sui_programmability/examples/defi/tests/escrow_tests.move delete mode 100644 sui_programmability/examples/defi/tests/flash_lender_tests.move delete mode 100644 sui_programmability/examples/defi/tests/shared_escrow_test.move delete mode 100644 sui_programmability/examples/ecommerce/Move.toml delete mode 100644 sui_programmability/examples/ecommerce/README.md delete mode 100644 sui_programmability/examples/ecommerce/sources/.gitkeep delete mode 100644 sui_programmability/examples/ecommerce/tests/.gitkeep delete mode 100644 sui_programmability/examples/fungible_tokens/Move.toml delete mode 100644 sui_programmability/examples/fungible_tokens/README.md delete mode 100644 sui_programmability/examples/fungible_tokens/sources/basket.move delete mode 100644 sui_programmability/examples/fungible_tokens/sources/managed.move delete mode 100644 sui_programmability/examples/fungible_tokens/sources/regulated_coin.move delete mode 100644 sui_programmability/examples/fungible_tokens/sources/treasury_lock.move delete mode 100644 sui_programmability/examples/fungible_tokens/tests/basket_tests.move delete mode 100644 sui_programmability/examples/games/Move.toml delete mode 100644 sui_programmability/examples/games/README.md delete mode 100644 sui_programmability/examples/games/sources/drand_based_lottery.move delete mode 100644 sui_programmability/examples/games/sources/drand_based_scratch_card.move delete mode 100644 sui_programmability/examples/games/sources/drand_lib.move delete mode 100644 sui_programmability/examples/games/sources/hero.move delete mode 100644 sui_programmability/examples/games/sources/rock_paper_scissors.move delete mode 100644 sui_programmability/examples/games/sources/sea_hero.move delete mode 100644 sui_programmability/examples/games/sources/sea_hero_helper.move delete mode 100644 sui_programmability/examples/games/sources/shared_tic_tac_toe.move delete mode 100644 sui_programmability/examples/games/sources/tic_tac_toe.move delete mode 100644 sui_programmability/examples/games/sources/vdf_based_lottery.move delete mode 100644 sui_programmability/examples/games/tests/drand_based_lottery_tests.move delete mode 100644 sui_programmability/examples/games/tests/drand_based_scratch_card_tests.move delete mode 100644 sui_programmability/examples/games/tests/rock_paper_scissors_tests.move delete mode 100644 sui_programmability/examples/games/tests/shared_tic_tac_toe_tests.move delete mode 100644 sui_programmability/examples/games/tests/tic_tac_toe_tests.move delete mode 100644 sui_programmability/examples/games/tests/vdf_based_lottery_tests.move delete mode 100644 sui_programmability/examples/move_tutorial/Move.toml delete mode 100644 sui_programmability/examples/move_tutorial/sources/my_module.move delete mode 100644 sui_programmability/examples/multi_package/README.md delete mode 100644 sui_programmability/examples/multi_package/dep_package/Move.toml delete mode 100644 sui_programmability/examples/multi_package/dep_package/sources/dep_module.move delete mode 100644 sui_programmability/examples/multi_package/main_package/Move.toml delete mode 100644 sui_programmability/examples/multi_package/main_package/sources/main_module.move delete mode 100644 sui_programmability/examples/nfts/Move.toml delete mode 100644 sui_programmability/examples/nfts/README.md delete mode 100644 sui_programmability/examples/nfts/sources/auction.move delete mode 100644 sui_programmability/examples/nfts/sources/auction_lib.move delete mode 100644 sui_programmability/examples/nfts/sources/chat.move delete mode 100644 sui_programmability/examples/nfts/sources/cross_chain_airdrop.move delete mode 100644 sui_programmability/examples/nfts/sources/devnet_nft.move delete mode 100644 sui_programmability/examples/nfts/sources/discount_coupon.move delete mode 100644 sui_programmability/examples/nfts/sources/geniteam.move delete mode 100644 sui_programmability/examples/nfts/sources/marketplace.move delete mode 100644 sui_programmability/examples/nfts/sources/num.move delete mode 100644 sui_programmability/examples/nfts/sources/shared_auction.move delete mode 100644 sui_programmability/examples/nfts/tests/auction_tests.move delete mode 100644 sui_programmability/examples/nfts/tests/chat_tests.move delete mode 100644 sui_programmability/examples/nfts/tests/cross_chain_airdrop_tests.move delete mode 100644 sui_programmability/examples/nfts/tests/discount_coupon_tests.move delete mode 100644 sui_programmability/examples/nfts/tests/shared_auction_tests.move delete mode 100644 sui_programmability/examples/objects_tutorial/Move.toml delete mode 100644 sui_programmability/examples/objects_tutorial/sources/color_object.move delete mode 100644 sui_programmability/examples/objects_tutorial/sources/simple_warrior.move delete mode 100644 sui_programmability/examples/objects_tutorial/sources/trusted_swap.move delete mode 100644 sui_programmability/examples/utils/Move.toml delete mode 100644 sui_programmability/examples/utils/sources/epoch_time_lock.move delete mode 100644 sui_programmability/examples/utils/sources/immutable_external_resource.move delete mode 100644 sui_programmability/examples/utils/sources/locked_coin.move delete mode 100644 sui_programmability/examples/utils/sources/safe.move delete mode 100644 sui_programmability/examples/utils/sources/typed_id.move delete mode 100644 sui_programmability/examples/utils/tests/immutable_external_resource_tests.move delete mode 100644 sui_programmability/examples/utils/tests/safe_tests.move diff --git a/sui_programmability/examples/README.md b/sui_programmability/examples/README.md index 7aec5e9887136..b2601469571c6 100644 --- a/sui_programmability/examples/README.md +++ b/sui_programmability/examples/README.md @@ -1,15 +1,2 @@ >[!IMPORTANT] -> These examples are out-of-date and have been retained as an archive, please **find the latest versions in the [examples](../../examples) directory** which contains up-to-date [Move](../../examples/move) and end-to-end examples! - -Lots of Move code examples, partitioned by category: - -* basics: The very simplest examples of Sui programming. -* crypto: A simple contract to perform ECDSA secp256k1 signature verification and ecrecover (derive the public key from a signature). -* defi: DeFi primitives like escrows, atomic swaps, flash loans, DEXes. -* fungible_tokens: Implementations of fungible tokens with different minting and burning policies. -* games: Various classic and not-so-classic on-chain games. -* nfts: Example NFT implementations and related functionality like auctions and marketplaces. - -We welcome third-party examples--please just submit a pull request! - -DISCLAIMER: This is example code provided for demonstration purposes only. These examples have not been thoroughly tested, verified, or audited. Please do not use the example code or derivatives in production without proper diligence. +> These examples have moved! **Find the latest versions in the [examples](../../examples) directory** which contains up-to-date [Move](../../examples/move) and end-to-end examples! diff --git a/sui_programmability/examples/basics/Move.toml b/sui_programmability/examples/basics/Move.toml deleted file mode 100644 index 71fda07567b2d..0000000000000 --- a/sui_programmability/examples/basics/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "Basics" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -basics = "0x0" diff --git a/sui_programmability/examples/basics/README.md b/sui_programmability/examples/basics/README.md deleted file mode 100644 index 3be04d1cb971d..0000000000000 --- a/sui_programmability/examples/basics/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Basics - -Very basic examples to illustrate the nuts and bolts of programming in Sui. - -* Object: a heavily commented example of a custom object. -* Sandwich: example of object exchange logic--combining ham and bread objects to produce a sandwich. -* Lock: example of a shared object which can be accessed if someone has a key. diff --git a/sui_programmability/examples/basics/sources/clock.move b/sui_programmability/examples/basics/sources/clock.move deleted file mode 100644 index be369d095122f..0000000000000 --- a/sui_programmability/examples/basics/sources/clock.move +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This example demonstrates reading a clock object. -/// Current time is emitted as an event in the get_time transaction -module basics::clock { - use sui::{clock::Clock, event}; - - public struct TimeEvent has copy, drop { - timestamp_ms: u64, - } - - /// Emit event with current time. - entry fun access(clock: &Clock) { - event::emit(TimeEvent { timestamp_ms: clock.timestamp_ms() }); - } -} diff --git a/sui_programmability/examples/basics/sources/counter.move b/sui_programmability/examples/basics/sources/counter.move deleted file mode 100644 index d79cb457d2959..0000000000000 --- a/sui_programmability/examples/basics/sources/counter.move +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This example demonstrates a basic use of a shared object. -/// Rules: -/// - anyone can create and share a counter -/// - everyone can increment a counter by 1 -/// - the owner of the counter can reset it to any value -module basics::counter { - /// A shared counter. - public struct Counter has key { - id: UID, - owner: address, - value: u64 - } - - public fun owner(counter: &Counter): address { - counter.owner - } - - public fun value(counter: &Counter): u64 { - counter.value - } - - /// Create and share a Counter object. - public entry fun create(ctx: &mut TxContext) { - transfer::share_object(Counter { - id: object::new(ctx), - owner: tx_context::sender(ctx), - value: 0 - }) - } - - /// Increment a counter by 1. - public entry fun increment(counter: &mut Counter) { - counter.value = counter.value + 1; - } - - /// Set value (only runnable by the Counter owner) - public entry fun set_value(counter: &mut Counter, value: u64, ctx: &TxContext) { - assert!(counter.owner == tx_context::sender(ctx), 0); - counter.value = value; - } - - /// Assert a value for the counter. - public entry fun assert_value(counter: &Counter, value: u64) { - assert!(counter.value == value, 0) - } - - /// Delete counter (only runnable by the Counter owner) - public entry fun delete(counter: Counter, ctx: &TxContext) { - assert!(counter.owner == tx_context::sender(ctx), 0); - let Counter {id, owner:_, value:_} = counter; - object::delete(id); - } -} - -#[test_only] -module basics::counter_test { - use sui::test_scenario; - use basics::counter; - - #[test] - fun test_counter() { - let owner = @0xC0FFEE; - let user1 = @0xA1; - - let mut scenario_val = test_scenario::begin(user1); - let scenario = &mut scenario_val; - - test_scenario::next_tx(scenario, owner); - { - counter::create(test_scenario::ctx(scenario)); - }; - - test_scenario::next_tx(scenario, user1); - { - let mut counter_val = test_scenario::take_shared(scenario); - let counter = &mut counter_val; - - assert!(counter::owner(counter) == owner, 0); - assert!(counter::value(counter) == 0, 1); - - counter::increment(counter); - counter::increment(counter); - counter::increment(counter); - test_scenario::return_shared(counter_val); - }; - - test_scenario::next_tx(scenario, owner); - { - let mut counter_val = test_scenario::take_shared(scenario); - let counter = &mut counter_val; - - assert!(counter::owner(counter) == owner, 0); - assert!(counter::value(counter) == 3, 1); - - counter::set_value(counter, 100, test_scenario::ctx(scenario)); - - test_scenario::return_shared(counter_val); - }; - - test_scenario::next_tx(scenario, user1); - { - let mut counter_val = test_scenario::take_shared(scenario); - let counter = &mut counter_val; - - assert!(counter::owner(counter) == owner, 0); - assert!(counter::value(counter) == 100, 1); - - counter::increment(counter); - - assert!(counter::value(counter) == 101, 2); - - test_scenario::return_shared(counter_val); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/basics/sources/lock.move b/sui_programmability/examples/basics/sources/lock.move deleted file mode 100644 index 6c58d125f9ebc..0000000000000 --- a/sui_programmability/examples/basics/sources/lock.move +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// An example of a module that uses Shared Objects and ID linking/access. -/// -/// This module allows any content to be locked inside a 'virtual chest' and later -/// be accessed by putting a 'key' into the 'lock'. Lock is shared and is visible -/// and discoverable by the key owner. -module basics::lock { - /// Lock is empty, nothing to take. - const ELockIsEmpty: u64 = 0; - - /// Key does not match the Lock. - const EKeyMismatch: u64 = 1; - - /// Lock already contains something. - const ELockIsFull: u64 = 2; - - /// Lock that stores any content inside it. - public struct Lock has key, store { - id: UID, - locked: Option - } - - /// A key that is created with a Lock; is transferable - /// and contains all the needed information to open the Lock. - public struct Key has key, store { - id: UID, - `for`: ID, - } - - /// Returns an ID of a Lock for a given Key. - public fun key_for(key: &Key): ID { - key.`for` - } - - /// Lock some content inside a shared object. A Key is created and is - /// sent to the transaction sender. - public fun create(obj: T, ctx: &mut TxContext): Key { - let id = object::new(ctx); - let `for` = object::uid_to_inner(&id); - - transfer::public_share_object(Lock { - id, - locked: option::some(obj), - }); - - Key { - `for`, - id: object::new(ctx) - } - } - - /// Lock something inside a shared object using a Key. Aborts if - /// lock is not empty or if key doesn't match the lock. - public fun lock( - obj: T, - lock: &mut Lock, - key: &Key, - ) { - assert!(option::is_none(&lock.locked), ELockIsFull); - assert!(&key.`for` == object::borrow_id(lock), EKeyMismatch); - - option::fill(&mut lock.locked, obj); - } - - /// Unlock the Lock with a Key and access its contents. - /// Can only be called if both conditions are met: - /// - key matches the lock - /// - lock is not empty - public fun unlock( - lock: &mut Lock, - key: &Key, - ): T { - assert!(option::is_some(&lock.locked), ELockIsEmpty); - assert!(&key.`for` == object::borrow_id(lock), EKeyMismatch); - - option::extract(&mut lock.locked) - } -} - -#[test_only] -module basics::lockTest { - use sui::test_scenario; - use basics::lock::{Self, Lock, Key}; - - /// Custom structure which we will store inside a Lock. - public struct Treasure has store, key { - id: UID - } - - #[test] - fun test_lock() { - let user1 = @0x1; - let user2 = @0x2; - - let mut scenario_val = test_scenario::begin(user1); - let scenario = &mut scenario_val; - - // User1 creates a lock and places his treasure inside. - test_scenario::next_tx(scenario, user1); - { - let ctx = test_scenario::ctx(scenario); - let id = object::new(ctx); - - let l = lock::create(Treasure { id }, ctx); - transfer::public_transfer(l, tx_context::sender(ctx)) - }; - - // Now User1 owns a key from the lock. He decides to send this - // key to User2, so that he can have access to the stored treasure. - test_scenario::next_tx(scenario, user1); - { - let key = test_scenario::take_from_sender>(scenario); - transfer::public_transfer(key, user2); - }; - - // User2 is impatient and he decides to take the treasure. - test_scenario::next_tx(scenario, user2); - { - let mut lock_val = test_scenario::take_shared>(scenario); - let lock = &mut lock_val; - let key = test_scenario::take_from_sender>(scenario); - let ctx = test_scenario::ctx(scenario); - - let l = lock::unlock(lock, &key); - transfer::public_transfer(l, tx_context::sender(ctx)); - - test_scenario::return_shared(lock_val); - test_scenario::return_to_sender(scenario, key); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/basics/sources/object.move b/sui_programmability/examples/basics/sources/object.move deleted file mode 100644 index 4ca7f764f76a3..0000000000000 --- a/sui_programmability/examples/basics/sources/object.move +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// An example of a custom object with comments explaining the relevant bits -module basics::object { - /// A custom sui object. Every object must have the `key` attribute - /// (indicating that it is allowed to be a key in the sui global object - /// pool), and must have a field `id: UID` corresponding to its sui ObjId. - /// Other object attributes present at the protocol level (authenticator, - /// sequence number, TxDigest, ...) are intentionally not exposed here. - public struct Object has key { - id: UID, - /// Custom objects can have fields of arbitrary type... - custom_field: u64, - /// ... including other objects - child_obj: ChildObject, - /// ... and other global objects - nested_obj: AnotherObject, - } - - /// An object that can be stored inside global objects or other child - /// objects, but cannot be placed in the global object pool on its own. - /// Note that it doesn't need an ID field. - public struct ChildObject has store { - a_field: bool, - } - - /// An object that can live either in the global object pool or as a nested - /// object. - public struct AnotherObject has key, store { - id: UID, - } - - /// Example of updating an object. All Move fields are private, so the - /// fields of `Object` can only be (directly) updated by code in this - /// module. - public fun write_field(o: &mut Object, v: u64) { - if (some_conditional_logic()) { - o.custom_field = v - } - } - - /// Example of transferring an object to a a new owner. A struct can only - /// be transferred by the module that declares it. - public fun transfer(o: Object, recipient: address) { - assert!(some_conditional_logic(), 0); - transfer::transfer(o, recipient) - } - - /// Simple getter - public fun read_field(o: &Object): u64 { - o.custom_field - } - - /// Example of creating a object by deriving a unique ID from the current - /// transaction and returning it to the caller (who may call functions - /// from this module to read/write it, package it into another object, ...) - public fun create(tx: &mut TxContext): Object { - Object { - id: object::new(tx), - custom_field: 0, - child_obj: ChildObject { a_field: false }, - nested_obj: AnotherObject { id: object::new(tx) } - } - } - - /// Example of an entrypoint function to be embedded in a Sui - /// transaction. A possible argument of an entrypoint function is a - /// `TxContext` created by the runtime that is useful for deriving - /// new id's or determining the sender of the transaction. - /// Next to the `TxContext`, entrypoints can take struct types with the `key` - /// attribute as input, as well as primitive types like ints, bools, ... - /// - /// A Sui transaction must declare the ID's of each object it will - /// access + any primitive inputs. The runtime that processes the - /// transaction fetches the values associated with the ID's, type-checks - /// the values + primitive inputs against the function signature - /// , then calls the `main` function with these values. - /// - /// If the script terminates successfully, the runtime collects changes to - /// input objects + created objects + emitted events, increments the - /// sequence number of each object, creates a hash that commits to the - /// outputs, etc. - public entry fun main( - to_read: &Object, // The argument of type Object is passed as a read-only reference - to_write: &mut Object, // The argument is passed as a mutable reference - to_consume: Object, // The argument is passed as a value - // ... end objects, begin primitive type inputs - int_input: u64, - recipient: address, - ctx: &mut TxContext, - ) { - let v = read_field(to_read); - write_field(to_write, v + int_input); - transfer(to_consume, recipient); - // demonstrate creating a new object for the sender - let sender = tx_context::sender(ctx); - transfer::transfer(create(ctx), sender) - } - - fun some_conditional_logic(): bool { - // placeholder for checks implemented in arbitrary Move code - true - } -} diff --git a/sui_programmability/examples/basics/sources/object_basics.move b/sui_programmability/examples/basics/sources/object_basics.move deleted file mode 100644 index a39d099298efc..0000000000000 --- a/sui_programmability/examples/basics/sources/object_basics.move +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Test CTURD object basics (create, transfer, update, read, delete) -module basics::object_basics { - use sui::event; - - public struct Object has key, store { - id: UID, - value: u64, - } - - public struct Wrapper has key { - id: UID, - o: Object - } - - public struct NewValueEvent has copy, drop { - new_value: u64 - } - - public entry fun create(value: u64, recipient: address, ctx: &mut TxContext) { - transfer::public_transfer( - Object { id: object::new(ctx), value }, - recipient - ) - } - - public entry fun transfer(o: Object, recipient: address) { - transfer::public_transfer(o, recipient) - } - - public entry fun freeze_object(o: Object) { - transfer::public_freeze_object(o) - } - - public entry fun set_value(o: &mut Object, value: u64) { - o.value = value; - } - - // test that reading o2 and updating o1 works - public entry fun update(o1: &mut Object, o2: &Object) { - o1.value = o2.value; - // emit an event so the world can see the new value - event::emit(NewValueEvent { new_value: o2.value }) - } - - public entry fun delete(o: Object) { - let Object { id, value: _ } = o; - object::delete(id); - } - - public entry fun wrap(o: Object, ctx: &mut TxContext) { - transfer::transfer(Wrapper { id: object::new(ctx), o }, tx_context::sender(ctx)) - } - - public entry fun unwrap(w: Wrapper, ctx: &TxContext) { - let Wrapper { id, o } = w; - object::delete(id); - transfer::public_transfer(o, tx_context::sender(ctx)) - } -} diff --git a/sui_programmability/examples/basics/sources/random.move b/sui_programmability/examples/basics/sources/random.move deleted file mode 100644 index f4dd9b88070a5..0000000000000 --- a/sui_programmability/examples/basics/sources/random.move +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This example demonstrates emitting a random u128 (e.g., for an offchain lottery) -module basics::random { - use sui::event; - use sui::random::Random; - - public struct RandomU128Event has copy, drop { - value: u128, - } - - entry fun new(r: &Random, ctx: &mut TxContext) { - let mut gen = r.new_generator(ctx); - let value = gen.generate_u128(); - event::emit(RandomU128Event { value }); - } -} diff --git a/sui_programmability/examples/basics/sources/sandwich.move b/sui_programmability/examples/basics/sources/sandwich.move deleted file mode 100644 index 18ecc13eec2b3..0000000000000 --- a/sui_programmability/examples/basics/sources/sandwich.move +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Example of objects that can be combined to create -/// new objects -module basics::sandwich { - use sui::balance::{Self, Balance}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - - public struct Ham has key, store { - id: UID - } - - public struct Bread has key, store { - id: UID - } - - public struct Sandwich has key, store { - id: UID, - } - - // This Capability allows the owner to withdraw profits - public struct GroceryOwnerCapability has key { - id: UID - } - - // Grocery is created on module init - public struct Grocery has key { - id: UID, - profits: Balance - } - - /// Price for ham - const HAM_PRICE: u64 = 10; - /// Price for bread - const BREAD_PRICE: u64 = 2; - - /// Not enough funds to pay for the good in question - const EInsufficientFunds: u64 = 0; - /// Nothing to withdraw - const ENoProfits: u64 = 1; - - #[allow(unused_function)] - /// On module init, create a grocery - fun init(ctx: &mut TxContext) { - transfer::share_object(Grocery { - id: object::new(ctx), - profits: balance::zero() - }); - - transfer::transfer(GroceryOwnerCapability { - id: object::new(ctx) - }, tx_context::sender(ctx)); - } - - /// Exchange `c` for some ham - public fun buy_ham( - grocery: &mut Grocery, - c: Coin, - ctx: &mut TxContext - ): Ham { - let b = coin::into_balance(c); - assert!(balance::value(&b) == HAM_PRICE, EInsufficientFunds); - balance::join(&mut grocery.profits, b); - Ham { id: object::new(ctx) } - } - - /// Exchange `c` for some bread - public fun buy_bread( - grocery: &mut Grocery, - c: Coin, - ctx: &mut TxContext - ): Bread { - let b = coin::into_balance(c); - assert!(balance::value(&b) == BREAD_PRICE, EInsufficientFunds); - balance::join(&mut grocery.profits, b); - Bread { id: object::new(ctx) } - } - - /// Combine the `ham` and `bread` into a delicious sandwich - public fun make_sandwich( - ham: Ham, bread: Bread, ctx: &mut TxContext - ): Sandwich { - let Ham { id: ham_id } = ham; - let Bread { id: bread_id } = bread; - object::delete(ham_id); - object::delete(bread_id); - Sandwich { id: object::new(ctx) } - } - - /// See the profits of a grocery - public fun profits(grocery: &Grocery): u64 { - balance::value(&grocery.profits) - } - - /// Owner of the grocery can collect profits by passing his capability - public fun collect_profits(_cap: &GroceryOwnerCapability, grocery: &mut Grocery, ctx: &mut TxContext): Coin { - let amount = balance::value(&grocery.profits); - - assert!(amount > 0, ENoProfits); - - // Take a transferable `Coin` from a `Balance` - coin::take(&mut grocery.profits, amount, ctx) - } - - #[test_only] - public fun init_for_testing(ctx: &mut TxContext) { - init(ctx); - } -} - -#[test_only] -module basics::test_sandwich { - use basics::sandwich::{Self, Grocery, GroceryOwnerCapability}; - use sui::test_scenario; - use sui::coin::{Self}; - use sui::sui::SUI; - use sui::test_utils; - - #[test] - fun test_make_sandwich() { - let owner = @0x1; - let the_guy = @0x2; - - let mut scenario_val = test_scenario::begin(owner); - let scenario = &mut scenario_val; - test_scenario::next_tx(scenario, owner); - { - sandwich::init_for_testing(test_scenario::ctx(scenario)); - }; - - test_scenario::next_tx(scenario, the_guy); - { - let mut grocery_val = test_scenario::take_shared(scenario); - let grocery = &mut grocery_val; - let ctx = test_scenario::ctx(scenario); - - let ham = sandwich::buy_ham( - grocery, - coin::mint_for_testing(10, ctx), - ctx - ); - - let bread = sandwich::buy_bread( - grocery, - coin::mint_for_testing(2, ctx), - ctx - ); - let sandwich = sandwich::make_sandwich(ham, bread, ctx); - - test_scenario::return_shared( grocery_val); - transfer::public_transfer(sandwich, tx_context::sender(ctx)) - }; - - test_scenario::next_tx(scenario, owner); - { - let mut grocery_val = test_scenario::take_shared(scenario); - let grocery = &mut grocery_val; - let capability = test_scenario::take_from_sender(scenario); - - assert!(sandwich::profits(grocery) == 12, 0); - let profits = sandwich::collect_profits(&capability, grocery, test_scenario::ctx(scenario)); - assert!(sandwich::profits(grocery) == 0, 0); - - test_scenario::return_to_sender(scenario, capability); - test_scenario::return_shared(grocery_val); - test_utils::destroy(profits) - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/capy/Move.toml b/sui_programmability/examples/capy/Move.toml deleted file mode 100644 index 809e0c09915ec..0000000000000 --- a/sui_programmability/examples/capy/Move.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "sui-capybaras" -version = "0.1.0" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -capy = "0x0" -me = "0x0" diff --git a/sui_programmability/examples/capy/sources/capy.move b/sui_programmability/examples/capy/sources/capy.move deleted file mode 100644 index c97d1e733aad1..0000000000000 --- a/sui_programmability/examples/capy/sources/capy.move +++ /dev/null @@ -1,486 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// The Capy module. Defines the Capy type and its functions. -module capy::capy { - use std::string::{Self, String}; - use sui::url::{Self, Url}; - use sui::event::emit; - use sui::dynamic_object_field as dof; - - use std::hash::sha3_256 as hash; - - /// Number of meaningful genes. Also marks the length - /// of the hash used in the application: sha3_256. - const GENES: u64 = 32; - - /// There's a chance to apply mutation on each gene. - /// For testing reasons mutation chance is relatively high: 5/255. - const MUTATION_CHANCE: u8 = 250; - - /// Base path for `Capy.url` attribute. Is temporary and improves - /// explorer / wallet display. Always points to the dev/testnet server. - const IMAGE_URL: vector = b"https://api.capy.art/capys/"; - - /// Link to the capy on the capy.art. - const MAIN_URL: vector = b"https://capy.art/capy/"; - - // ======== Types ========= - - /// Internal representation of a Gene Sequence. Each Gene - /// corresponds to an attribute. Max number of genes is `GENES`. - public struct Genes has store, copy, drop { sequence: vector } - - /// Defines a Capy attribute. Eg: `pattern: 'panda'` - public struct Attribute has store, copy, drop { - name: String, - value: String, - } - - /// One of the possible values defined in GeneDefinition. - /// `selector` field corresponds to the u8 value of the gene in a sequence. - /// - /// See `breed` function for details on usage. - public struct Value has store, drop, copy { - selector: u8, - name: String - } - - /// Holds the definitions for each gene. They are then assigned to - /// Capys. Newborn will receive attributes available at the time. - public struct GeneDefinition has store { - name: String, - values: vector - } - - /// The Capy itself. Every Capy has its unique set of genes, - /// as well as generation and parents. Ownable, tradeable. - public struct Capy has key, store { - id: UID, - gen: u32, - url: Url, - link: Url, - genes: Genes, - dev_genes: Genes, - item_count: u8, - attributes: vector, - } - - /// Belongs to the creator of the game. Has store, which - /// allows building something on top of it (ie shared object with - /// multi-access policy for managers). - public struct CapyManagerCap has key, store { id: UID } - - /// Every capybara is registered here. Acts as a source of randomness - /// as well as the storage for the main information about the gamestate. - public struct CapyRegistry has key { - id: UID, - capy_born: u64, - capy_hash: vector, - genes: vector - } - - - // ======== Events ========= - - /// Event. When a new registry has been created. - /// Marks the start of the game. - public struct RegistryCreated has copy, drop { id: ID } - - /// Event. Emitted when a new Gene definition was added. - /// Helps announcing new features. - public struct GeneDefinitionAdded has copy, drop { - name: String, - values: vector - } - - /// Event. When new Capy is born. - public struct CapyBorn has copy, drop { - id: ID, - gen: u32, - genes: Genes, - dev_genes: Genes, - attributes: vector, - parent_one: Option, - parent_two: Option, - bred_by: address - } - - /// Event. Emitted when a new item is added to a capy. - public struct ItemAdded has copy, drop { - capy_id: ID, - item_id: ID - } - - /// Event. Emitted when an item is taken off. - public struct ItemRemoved has copy, drop { - capy_id: ID, - item_id: ID, - } - - // ======== View Functions ======== - - /// Read extra gene sequence of a Capy as `vector`. - public fun dev_genes(self: &Capy): &vector { - &self.dev_genes.sequence - } - - // ======== Functions ========= - - #[allow(unused_function)] - /// Create a shared CapyRegistry and give its creator the capability - /// to manage the game. - fun init(ctx: &mut TxContext) { - let id = object::new(ctx); - let capy_hash = hash(object::uid_to_bytes(&id)); - - emit(RegistryCreated { id: object::uid_to_inner(&id) }); - - transfer::public_transfer(CapyManagerCap { id: object::new(ctx) }, tx_context::sender(ctx)); - transfer::share_object(CapyRegistry { - id, - capy_hash, - capy_born: 0, - genes: vector[], - }) - } - - - // ======= Admin Functions ======= - - /// This method is rather complicated. - /// To define a new set of attributes, Admin must send it in a format: - /// ``` - /// name = b"name of the attribute" - /// definitions = [ - /// [selector_u8, ...name_bytes], - /// [selector_u8, ...name_bytes] - /// ] - /// ``` - public entry fun add_gene( - _: &CapyManagerCap, - reg: &mut CapyRegistry, - name: vector, - definitions: vector>, - _ctx: &mut TxContext - ) { - let name = string::utf8(name); - let values = raw_vec_to_values(definitions); - - // emit an event confirming gene addition - emit(GeneDefinitionAdded { name: *&name, values: *&values }); - - // lastly add new gene definition to the registry - reg.genes.push_back(GeneDefinition { name, values }); - } - - /// Batch-add new Capys with predefined gene sequences. - public fun batch(_: &CapyManagerCap, reg: &mut CapyRegistry, mut genes: vector>, ctx: &mut TxContext): vector { - let mut capys = vector[]; - while (genes.length() > 0) { - let sequence = genes.pop_back(); - let capy = create_capy(reg, sequence, vector[], ctx); - - capys.push_back(capy); - }; - - capys - } - - /// Creates an attribute with the given name and a value. Should only be used for - /// events. Is currently a friend-only feature but will be put behind a capability - /// authorization later. - public(package) fun create_attribute(name: vector, value: vector): Attribute { - Attribute { - name: string::utf8(name), - value: string::utf8(value) - } - } - - /// Create a Capy with a specified gene sequence. - /// Also allows assigning custom attributes if an App is authorized to do it. - public(package) fun create_capy( - reg: &mut CapyRegistry, sequence: vector, custom_attributes: vector, ctx: &mut TxContext - ): Capy { - let id = object::new(ctx); - let genes = Genes { sequence }; - let dev_genes = Genes { sequence: hash(sequence) }; - - reg.capy_born = reg.capy_born + 1; - - reg.capy_hash.append(object::uid_to_bytes(&id)); - reg.capy_hash = hash(reg.capy_hash); - - let sender = tx_context::sender(ctx); - let mut attributes = get_attributes(®.genes, &genes); - - attributes.append(custom_attributes); - - emit(CapyBorn { - id: object::uid_to_inner(&id), - gen: 0, - attributes: *&attributes, - genes: *&genes, - dev_genes: *&dev_genes, - parent_one: option::none(), - parent_two: option::none(), - bred_by: sender - }); - - Capy { - url: img_url(&id), - link: link_url(&id), - id, - genes, - dev_genes, - attributes, - gen: 0, - item_count: 0, - } - } - - // ======= User facing functions ======= - - /// Attach an Item to a Capy. Function is generic and allows any app to attach items to - /// Capys but the total count of items has to be lower than 255. - public entry fun add_item(capy: &mut Capy, item: T) { - emit(ItemAdded { - capy_id: object::id(capy), - item_id: object::id(&item) - }); - - dof::add(&mut capy.id, object::id(&item), item); - } - - /// Remove item from the Capy. - public entry fun remove_item(capy: &mut Capy, item_id: ID, ctx: &TxContext) { - emit(ItemRemoved { - capy_id: object::id(capy), - item_id: *&item_id - }); - - transfer::public_transfer(dof::remove(&mut capy.id, item_id), tx_context::sender(ctx)); - } - - /// Breed capys and keep the newborn at sender's address. - public entry fun breed_and_keep( - reg: &mut CapyRegistry, - c1: &mut Capy, - c2: &mut Capy, - ctx: &mut TxContext - ) { - transfer::public_transfer(breed(reg, c1, c2, ctx), tx_context::sender(ctx)) - } - - /// Breed two Capys together. Perform a gene science algorithm and select - /// genes for the newborn based on the parents' genes. - public fun breed( - reg: &mut CapyRegistry, - c1: &mut Capy, - c2: &mut Capy, - ctx: &mut TxContext - ): Capy { - let id = object::new(ctx); - - // Update capy hash in the registry - reg.capy_hash.append(object::uid_to_bytes(&id)); - - // compute genes - reg.capy_hash = hash(reg.capy_hash); - let genes = compute_genes(®.capy_hash, &c1.genes, &c2.genes, GENES); - - // compute dev-genes - reg.capy_hash = hash(reg.capy_hash); - let dev_genes = compute_genes(®.capy_hash, &c1.genes, &c2.genes, GENES); - - let gen = if (c1.gen > c2.gen) { c1.gen } else { c2.gen } + 1; - let attributes = get_attributes(®.genes, &genes); - let sender = tx_context::sender(ctx); - - emit(CapyBorn { - id: object::uid_to_inner(&id), - gen, - genes: *&genes, - attributes: *&attributes, - dev_genes: *&dev_genes, - parent_one: option::some(object::id(c1)), - parent_two: option::some(object::id(c2)), - bred_by: sender - }); - - // Send newborn to parents. - Capy { - url: img_url(&id), - link: link_url(&id), - id, - gen, - genes, - dev_genes, - attributes, - item_count: 0, - } - } - - // ======= Private and Utility functions ======= - - /// Get Capy attributes from the gene sequence. - fun get_attributes(definitions: &vector, genes: &Genes): vector { - let mut attributes = vector[]; - let (mut i, len) = (0u64, definitions.length()); - while (i < len) { - let gene_def = &definitions[i]; - let capy_gene = &genes.sequence[i]; - - let (mut j, num_options) = (0u64, gene_def.values.length()); - while (j < num_options) { - let value = &gene_def.values[j]; - if (*capy_gene <= value.selector) { - attributes.push_back(Attribute { - name: *&gene_def.name, - value: *&value.name - }); - break - }; - j = j + 1; - }; - i = i + 1; - }; - - attributes - } - - /// Computes genes for the newborn based on the random seed r0, and parents genes - /// The `max` parameter affects how many genes should be changed (if there are no - /// attributes yet for the) - fun compute_genes(r0: &vector, g1: &Genes, g2: &Genes, max: u64): Genes { - let mut i = 0; - - let s1 = &g1.sequence; - let s2 = &g2.sequence; - let mut s3 = vector[]; - - let r1 = derive(r0, 1); // for parent gene selection - let r2 = derive(r0, 2); // chance of random mutation - let r3 = derive(r0, 3); // value selector for random mutation - - while (i < max) { - let rng = r1[i]; - let mut gene = if (lor(rng, 127)) { - s1[i] - } else { - s2[i] - }; - - // There's a tiny chance that a mutation will happen. - if (lor(r2[i], MUTATION_CHANCE)) { - gene = r3[i]; - }; - - s3.push_back(gene); - i = i + 1; - }; - - Genes { sequence: s3 } - } - - /// Give true or false based on the number. - /// Used for selecting mother/father genes. - fun lor(rng: u8, cmp: u8): bool { - (rng > cmp) - } - - /// Derive something from the seed. Add a derivation path as u8, and - /// hash the result. - fun derive(r0: &vector, path: u8): vector { - let mut r1 = *r0; - r1.push_back(path); - hash(r1) - } - - - // ==== Utilities ====== - - /// Transforms a vector of raw definitions: - /// [ - /// [127, b"red"], - /// [255, b"blue"], - /// ] - /// Into a vector of `Value`s (in order!): - /// [ - /// Value { selector: 127, name: String("red") }, - /// Value { selector: 255, name: String("blue") }, - /// ] - fun raw_vec_to_values(mut definitions: vector>): vector { - let mut result = vector[]; - definitions.reverse(); - while (definitions.length() > 0) { - // [selector, name] - let mut value_def = definitions.pop_back(); - // [eman, selector] - value_def.reverse(); - let selector = value_def.pop_back(); - let mut name = vector[]; - while (value_def.length() > 0) { - name.push_back(value_def.pop_back()); - }; - - result.push_back(Value { - selector, - name: string::utf8(name) - }); - }; - - result - } - - /// Construct an image URL for the capy. - fun img_url(c: &UID): Url { - let mut capy_url = IMAGE_URL; - capy_url.append(sui::hex::encode(object::uid_to_bytes(c))); - capy_url.append(b"/svg"); - - url::new_unsafe_from_bytes(capy_url) - } - - /// Construct a Url to the capy.art. - fun link_url(c: &UID): Url { - let mut capy_url = MAIN_URL; - capy_url.append(sui::hex::encode(object::uid_to_bytes(c))); - url::new_unsafe_from_bytes(capy_url) - } - - #[test] - fun test_raw_vec_to_values() { - let mut definitions: vector> = vector[]; - - /* push [127, "red"] */ { - let mut def = vector[]; - def.push_back(127); - def.append(b"red"); - definitions.push_back(def); - }; - - /* push [255, "blue"] */ { - let mut def = vector[]; - def.push_back(255); - def.append(b"blue"); - definitions.push_back(def); - }; - - let mut values: vector = raw_vec_to_values(definitions); - - /* expect [255, blue] */ { - let Value { selector, name } = values.pop_back(); - assert!(selector == 255, 0); - assert!(name.as_bytes() == &b"blue", 0); - }; - - /* expect [127, red] */ { - let Value { selector, name } = values.pop_back(); - assert!(selector == 127, 0); - assert!(name.as_bytes() == &b"red", 0); - }; - - values.destroy_empty(); - } -} diff --git a/sui_programmability/examples/capy/sources/capy_admin.move b/sui_programmability/examples/capy/sources/capy_admin.move deleted file mode 100644 index 153003a16f688..0000000000000 --- a/sui_programmability/examples/capy/sources/capy_admin.move +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Encapsulates CapyAdmin functionality allowing for faster -/// txs and lower gas fees for new capy / item batches. -module capy::capy_admin { - use capy::capy::{Self, CapyManagerCap, CapyRegistry, Capy}; - use capy::capy_market::{Self, CapyMarket}; - - entry fun add_gene( - cap: &CapyManagerCap, - reg: &mut CapyRegistry, - name: vector, - definitions: vector>, - ctx: &mut TxContext - ) { - capy::add_gene(cap, reg, name, definitions, ctx); - } - - entry fun batch_sell_capys( - cap: &CapyManagerCap, - reg: &mut CapyRegistry, - market: &mut CapyMarket, - genes: vector>, - price: u64, - ctx: &mut TxContext - ) { - let capys = capy::batch(cap, reg, genes, ctx); - capy_market::batch_list(market, capys, price, ctx); - } -} diff --git a/sui_programmability/examples/capy/sources/capy_item.move b/sui_programmability/examples/capy/sources/capy_item.move deleted file mode 100644 index 53a55fdb4b9a4..0000000000000 --- a/sui_programmability/examples/capy/sources/capy_item.move +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Store for Capys. Unlike Marketplace, Store sells identical items -/// in the limit specified quantity or, if quantity is not set, unlimited. -/// -/// Gives the Store Owner full access over the Listings and their quantity -/// as well as allows collecting profits in a single call. -module capy::capy_item { - use sui::url::{Self, Url}; - use std::string::{Self, String}; - use sui::sui::SUI; - use sui::balance::{Self, Balance}; - use sui::dynamic_object_field as dof; - use sui::coin::{Self, Coin}; - use sui::event::emit; - use sui::pay; - - /// Base path for `CapyItem.url` attribute. Is temporary and improves - /// explorer / wallet display. Always points to the dev/testnet server. - const IMAGE_URL: vector = b"https://api.capy.art/items/"; - - /// Store for any type T. Collects profits from all sold listings - /// to be later acquirable by the Capy Admin. - public struct ItemStore has key { - id: UID, - balance: Balance - } - - /// A Capy item, that is being purchased from the `ItemStore`. - public struct CapyItem has key, store { - id: UID, - name: String, - /// Urls and other meta information should - /// always go last as it allows for partial - /// deserialization of data on the frontend - url: Url, - } - - /// A Capability granting the bearer full control over the `ItemStore`. - public struct StoreOwnerCap has key, store { id: UID } - - /// A listing for an Item. Supply is either finite or infinite. - public struct ListedItem has key, store { - id: UID, - url: Url, - name: String, - `type`: String, - price: u64, - quantity: Option, - } - - /// Emitted when new item is purchased. - /// Off-chain we only need to know which ID - /// corresponds to which name to serve the data. - public struct ItemCreated has copy, drop { - id: ID, - name: String, - } - - #[allow(unused_function)] - /// Create a `ItemStore` and a `StoreOwnerCap` for this store. - fun init(ctx: &mut TxContext) { - transfer::share_object(ItemStore { - id: object::new(ctx), - balance: balance::zero() - }); - - transfer::public_transfer(StoreOwnerCap { - id: object::new(ctx) - }, ctx.sender()) - } - - /// Admin action - collect Profits from the `ItemStore`. - public entry fun collect_profits( - _: &StoreOwnerCap, s: &mut ItemStore, ctx: &mut TxContext - ) { - let a = balance::value(&s.balance); - let b = balance::split(&mut s.balance, a); - - transfer::public_transfer(coin::from_balance(b, ctx), ctx.sender()) - } - - /// Change the quantity value for the listing in the `ItemStore`. - public entry fun set_quantity( - _: &StoreOwnerCap, s: &mut ItemStore, name: vector, quantity: u64 - ) { - let listing_mut = dof::borrow_mut, ListedItem>(&mut s.id, name); - option::swap(&mut listing_mut.quantity, quantity); - } - - /// List an item in the `ItemStore` to be freely purchasable - /// within the set quantity (if set). - public entry fun sell( - _: &StoreOwnerCap, - s: &mut ItemStore, - name: vector, - `type`: vector, - price: u64, - // quantity: Option, - ctx: &mut TxContext - ) { - dof::add(&mut s.id, name, ListedItem { - id: object::new(ctx), - url: img_url(name), - price, - quantity: option::none(), // temporarily only infinite quantity - name: string::utf8(name), - `type`: string::utf8(`type`) - }); - } - - /// Buy an Item from the `ItemStore`. Pay `Coin` and - /// receive a `CapyItem`. - public entry fun buy_and_take( - s: &mut ItemStore, name: vector, payment: Coin, ctx: &mut TxContext - ) { - let listing_mut = dof::borrow_mut, ListedItem>(&mut s.id, name); - - // check that the Coin amount matches the price; then add it to the balance - assert!(coin::value(&payment) == listing_mut.price, 0); - coin::put(&mut s.balance, payment); - - // if quantity is set, make sure that it's not 0; then decrement - if (option::is_some(&listing_mut.quantity)) { - let q = option::borrow(&listing_mut.quantity); - assert!(*q > 0, 0); - option::swap(&mut listing_mut.quantity, *q - 1); - }; - - let id = object::new(ctx); - - emit(ItemCreated { - id: object::uid_to_inner(&id), - name: listing_mut.name - }); - - transfer::public_transfer(CapyItem { - id, - url: listing_mut.url, - name: listing_mut.name, - }, ctx.sender()) - } - - /// Buy a CapyItem with a single Coin which may be bigger than the - /// price of the listing. - public entry fun buy_mut( - s: &mut ItemStore, name: vector, payment: &mut Coin, ctx: &mut TxContext - ) { - let listing = dof::borrow, ListedItem>(&s.id, name); - let paid = coin::split(payment, listing.price, ctx); - buy_and_take(s, name, paid, ctx) - } - - /// Buy a CapyItem with multiple Coins by joining them first and then - /// calling the `buy_mut` function. - public entry fun buy_mul_coin( - s: &mut ItemStore, name: vector, mut coins: vector>, ctx: &mut TxContext - ) { - let mut paid = coins.pop_back(); - pay::join_vec(&mut paid, coins); - buy_mut(s, name, &mut paid, ctx); - transfer::public_transfer(paid, ctx.sender()) - } - - /// Construct an image URL for the `CapyItem`. - fun img_url(name: vector): Url { - let mut capy_url = IMAGE_URL; - capy_url.append(name); - capy_url.append(b"/svg"); - - url::new_unsafe_from_bytes(capy_url) - } -} diff --git a/sui_programmability/examples/capy/sources/capy_market.move b/sui_programmability/examples/capy/sources/capy_market.move deleted file mode 100644 index bbe8ff5f049ad..0000000000000 --- a/sui_programmability/examples/capy/sources/capy_market.move +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// CapyMarket for Capy-related objects. -/// Allows selling and accessories. -/// -/// TODO: refactor usage of events - many of the parameters are redundant -/// and can be linked off-chain with additional tooling. Kept for usability -/// and development speed purposes. -module capy::capy_market { - use sui::pay; - use sui::sui::SUI; - use sui::event::emit; - use sui::coin::{Self, Coin}; - use sui::dynamic_object_field as dof; - - // The Capy Manager gains all control over admin actions - // of the capy_marketplace. Modules must be published together - // to achieve consistency over types. - use capy::capy::{Capy, CapyManagerCap}; - - /// For when someone tries to delist without ownership. - const ENotOwner: u64 = 0; - - /// For when amount paid does not match the expected. - const EAmountIncorrect: u64 = 1; - - /// For when there's nothing to claim from the marketplace. - const ENoProfits: u64 = 2; - - // ======= Types ======= - - /// A generic marketplace for anything. - public struct CapyMarket has key { - id: UID, - } - - /// A listing for the marketplace. Intermediary object which owns an Item. - public struct Listing has key, store { - id: UID, - price: u64, - owner: address, - } - - // ======= Events ======= - - /// Emitted when a new CapyMarket is created. - public struct MarketCreated has copy, drop { - market_id: ID, - } - - /// Emitted when someone lists a new item on the CapyMarket. - public struct ItemListed has copy, drop { - listing_id: ID, - item_id: ID, - price: u64, - owner: address, - } - - /// Emitted when owner delists an item from the CapyMarket. - public struct ItemDelisted has copy, drop { - listing_id: ID, - item_id: ID, - } - - /// Emitted when someone makes a purchase. `new_owner` shows - /// who's a happy new owner of the purchased item. - public struct ItemPurchased has copy, drop { - listing_id: ID, - item_id: ID, - new_owner: address, - } - - /// For when someone collects profits from the market. Helps - /// indexer show who has how much. - public struct ProfitsCollected has copy, drop { - owner: address, - amount: u64 - } - - // ======= Publishing ======= - - #[allow(unused_function)] - /// By default create two Markets - fun init(ctx: &mut TxContext) { - publish(ctx); - } - - /// Admin-only method which allows creating a new marketplace. - public entry fun create_marketplace( - _: &CapyManagerCap, ctx: &mut TxContext - ) { - publish(ctx) - } - - /// Publish a new CapyMarket for any type T. Method is private and - /// can only be called in a module initializer or in an admin-only - /// method `create_marketplace` - fun publish(ctx: &mut TxContext) { - let id = object::new(ctx); - emit(MarketCreated { market_id: object::uid_to_inner(&id) }); - transfer::share_object(CapyMarket { id }); - } - - // ======= CapyMarket Actions ======= - - /// List a batch of T at once. - public fun batch_list( - market: &mut CapyMarket, - mut items: vector, - price: u64, - ctx: &mut TxContext - ) { - while (items.length() > 0) { - list(market, items.pop_back(), price, ctx) - }; - - items.destroy_empty(); - } - - /// List a new item on the CapyMarket. - public entry fun list( - market: &mut CapyMarket, - item: T, - price: u64, - ctx: &mut TxContext - ) { - let id = object::new(ctx); - let owner = tx_context::sender(ctx); - let mut listing = Listing { id, price, owner }; - - emit(ItemListed { - item_id: object::id(&item), - listing_id: object::id(&listing), - price, - owner - }); - - // Attach Item to the Listing through listing.id; - // Then attach listing to the marketplace through item_id; - dof::add(&mut listing.id, true, item); - dof::add(&mut market.id, object::id(&listing), listing); - } - - /// Remove listing and get an item back. Only owner can do that. - public fun delist( - market: &mut CapyMarket, - listing_id: ID, - ctx: &TxContext - ): T { - let Listing { mut id, price: _, owner } = dof::remove(&mut market.id, listing_id); - let item = dof::remove(&mut id, true); - - assert!(tx_context::sender(ctx) == owner, ENotOwner); - - emit(ItemDelisted { - listing_id, - item_id: object::id(&item), - }); - - object::delete(id); - item - } - - /// Call [`delist`] and transfer item to the sender. - entry fun delist_and_take( - market: &mut CapyMarket, - listing_id: ID, - ctx: &TxContext - ) { - transfer::public_transfer( - delist(market, listing_id, ctx), - tx_context::sender(ctx) - ) - } - - /// Withdraw profits from the marketplace as a single Coin (accumulated as a DOF). - /// Uses sender of transaction to determine storage and control access. - entry fun take_profits( - market: &mut CapyMarket, - ctx: &TxContext - ) { - let sender = tx_context::sender(ctx); - assert!(dof::exists_(&market.id, sender), ENoProfits); - let profit = dof::remove>(&mut market.id, sender); - - emit(ProfitsCollected { - owner: sender, - amount: coin::value(&profit) - }); - - transfer::public_transfer(profit, sender) - } - - /// Purchase an item using a known Listing. Payment is done in Coin. - /// Amount paid must match the requested amount. If conditions are met, - /// owner of the item gets the payment and buyer receives their item. - public fun purchase( - market: &mut CapyMarket, - listing_id: ID, - paid: Coin, - ctx: &TxContext - ): T { - let Listing { mut id, price, owner } = dof::remove(&mut market.id, listing_id); - let item = dof::remove(&mut id, true); - let new_owner = tx_context::sender(ctx); - - assert!(price == coin::value(&paid), EAmountIncorrect); - - emit(ItemPurchased { - item_id: object::id(&item), - listing_id, - new_owner - }); - - // if there's a balance attached to the marketplace - merge it with paid. - // if not -> leave a Coin hanging as a dynamic field of the marketplace. - if (dof::exists_(&market.id, owner)) { - coin::join(dof::borrow_mut>(&mut market.id, owner), paid) - } else { - dof::add(&mut market.id, owner, paid) - }; - - object::delete(id); - item - } - - /// Call [`buy`] and transfer item to the sender. - entry fun purchase_and_take( - market: &mut CapyMarket, - listing_id: ID, - paid: Coin, - ctx: &TxContext - ) { - transfer::public_transfer( - purchase(market, listing_id, paid, ctx), - tx_context::sender(ctx) - ) - } - - /// Use `&mut Coin` to purchase `T` from marketplace. - entry fun purchase_and_take_mut( - market: &mut CapyMarket, - listing_id: ID, - paid: &mut Coin, - ctx: &mut TxContext - ) { - let listing = dof::borrow(&market.id, *&listing_id); - let coin = coin::split(paid, listing.price, ctx); - purchase_and_take(market, listing_id, coin, ctx) - } - - /// Send multiple Coins in order to merge them and afford pricy Capy. - entry fun purchase_and_take_mul_coins( - market: &mut CapyMarket, - listing_id: ID, - mut coins: vector>, - ctx: &mut TxContext - ) { - let listing = dof::borrow(&market.id, *&listing_id); - let mut coin = coins.pop_back(); - - pay::join_vec(&mut coin, coins); - - let paid = coin::split(&mut coin, listing.price, ctx); - transfer::public_transfer(coin, tx_context::sender(ctx)); - purchase_and_take(market, listing_id, paid, ctx) - } -} diff --git a/sui_programmability/examples/capy/sources/capy_winter.move b/sui_programmability/examples/capy/sources/capy_winter.move deleted file mode 100644 index 9e49857d1b51a..0000000000000 --- a/sui_programmability/examples/capy/sources/capy_winter.move +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This module enables Capy Winter Event - the result of a -/// unique collaboration between Capy Labs and Capy Post. -/// -/// Once a year, two giants of the Capy world unite their -/// forces to deliver the best Winter Holiday experience to -/// support kindness, generosity and the holiday mood. -/// -/// Capy Post takes zero commission for gift parcels. -module capy::capy_winter { - use sui::sui::SUI; - use sui::coin::{Self, Coin}; - use sui::balance::{Self, Balance}; - use std::hash::sha3_256 as hash; - use sui::dynamic_field as df; - use sui::url::{Self, Url}; - use sui::event::emit; - use sui::pay; - use sui::bcs; - - use capy::capy::{Self, Attribute, CapyRegistry}; - - /// The name for custom attributes. - const ATTRIBUTE_NAME: vector = b"special"; - - /// Custom attributes assigned randomly when a box is opened. - const ATTRIBUTE_VALUES: vector> = vector[ - b"snow globe", - b"antlers", - b"garland", - b"beard", - ]; - - /// Value for the premium attribute. - const PREMIUM_ATTRIBUTE: vector = b"winter environment"; - - /// Total number of different GiftBoxes available for - /// sale from the CapyPost. - const GIFT_TYPES: u8 = 8; - - /// A single price for every GiftBox available this year. - const GIFT_PRICE: u64 = 2023_0000; - - /// Position of the '0' symbol in ASCII - const ASCII_OFFSET: u8 = 48; - - /// A gift box; what's inside? - public struct GiftBox has key { - id: UID, - `type`: u8, - url: Url, - link: Url, - } - - /// A ticket granting the permission to buy a premium box. - public struct PremiumTicket has key { id: UID } - - /// A Premium box - can only be purchased by the most genereous givers. - public struct PremiumBox has key { - id: UID, - url: Url, - } - - /// Every parcel must go through here! - public struct CapyPost has key { id: UID, balance: Balance } - - // ========= Events ========= - - /// Emitted when a box was purchased of a gift box. - public struct GiftPurchased has copy, drop { id: ID, `type`: u8 } - - /// Emitted when a gift has been sent - public struct GiftSent has copy, drop { id: ID } - - /// Emitted when a gift was opened! - public struct GiftOpened has copy, drop { id: ID } - - /// Emitted when a premium gift was received. - public struct PremiumTicketReceived has copy, drop { id: ID } - - /// Emitted when a premium box was purchased. - public struct PremiumPurchased has copy, drop { id: ID } - - /// Emitted when a premium gift was opened. - public struct PremiumOpened has copy, drop { id: ID } - - // ========= Dynamic Parameters Keys ========= - - public struct SentKey has store, copy, drop { sender: address } - - #[allow(unused_function)] - /// Build a CapyPost office and offer gifts to send and buy. - fun init(ctx: &mut TxContext) { - transfer::share_object(CapyPost { id: object::new(ctx), balance: balance::zero() }); - } - - /// Buy a single `GiftBox` and keep it at the sender's address. - entry fun buy_gift(post: &mut CapyPost, `type`: u8, payment: vector>, ctx: &mut TxContext) { - assert!(`type` < GIFT_TYPES, 0); - - let (paid, remainder) = merge_and_split(payment, GIFT_PRICE, ctx); - coin::put(&mut post.balance, paid); - let id = object::new(ctx); - let url = get_img_url(`type`); - let link = get_link_url(&id, `type`); - - emit(GiftPurchased { id: object::uid_to_inner(&id), `type` }); - transfer::transfer(GiftBox { id, `type`, url, link }, ctx.sender()); - transfer::public_transfer(remainder, ctx.sender()) - } - - /// Send a GiftBox to a friend or a stranger through CapyPost. - /// Kindness and generosity will be rewarded! - entry fun send_gift(post: &mut CapyPost, box: GiftBox, receiver: address, ctx: &mut TxContext) { - let sender = ctx.sender(); - - // Can't send gifts to yourself... - assert!(receiver != sender, 0); - - // If there's already a gift-tracking field, we increment the counter; - // Once it reaches 2 (the third send), we reset the counter and send a PremiumBox; - let sent = if (df::exists_with_type(&post.id, SentKey { sender })) { - let sent = df::remove(&mut post.id, SentKey { sender }); - if (sent == 1) { - let id = object::new(ctx); - emit(PremiumTicketReceived { id: object::uid_to_inner(&id) }); - transfer::transfer(PremiumTicket { id }, ctx.sender()); - 0 - } else { sent + 1 } - } else { 0 }; - - // update the counter with the resulting value - df::add(&mut post.id, SentKey { sender }, sent); - - emit(GiftSent { id: object::id(&box) }); - transfer::transfer(box, receiver) - } - - /// Open a box and expect a surprise! - entry fun open_box(reg: &mut CapyRegistry, box: GiftBox, ctx: &mut TxContext) { - let GiftBox { id, `type`: _, url: _, link: _ } = box; - let sequence = std::hash::sha3_256(object::uid_to_bytes(&id)); - let attribute = get_attribute(&sequence); - - emit(GiftOpened { id: object::uid_to_inner(&id) }); - transfer::public_transfer(capy::create_capy(reg, sequence, vector[ attribute ], ctx), ctx.sender()); - object::delete(id) - } - - /// Buy a premium box using a ticket! - entry fun buy_premium( - post: &mut CapyPost, ticket: PremiumTicket, payment: vector>, ctx: &mut TxContext - ) { - let PremiumTicket { id: ticket_id } = ticket; - let (paid, remainder) = merge_and_split(payment, GIFT_PRICE, ctx); - coin::put(&mut post.balance, paid); - let id = object::new(ctx); - - emit(PremiumPurchased { id: object::uid_to_inner(&id) }); - transfer::transfer(PremiumBox { id, url: get_img_url(99) }, ctx.sender()); - transfer::public_transfer(remainder, ctx.sender()); - object::delete(ticket_id) - } - - /// Open a premium box! - entry fun open_premium(reg: &mut CapyRegistry, box: PremiumBox, ctx: &mut TxContext) { - let PremiumBox { id, url: _ } = box; - let sequence = std::hash::sha3_256(object::uid_to_bytes(&id)); - let premium = capy::create_attribute(ATTRIBUTE_NAME, PREMIUM_ATTRIBUTE); - - emit(PremiumOpened { id: object::uid_to_inner(&id) }); - transfer::public_transfer(capy::create_capy(reg, sequence, vector[ premium ], ctx), ctx.sender()); - object::delete(id) - } - - /// Merges a vector of Coin then splits the `amount` from it, returns the - /// Coin with the amount and the remainder. - fun merge_and_split( - mut coins: vector>, amount: u64, ctx: &mut TxContext - ): (Coin, Coin) { - let mut base = coins.pop_back(); - pay::join_vec(&mut base, coins); - assert!(coin::value(&base) > amount, 0); - (coin::split(&mut base, amount, ctx), base) - } - - /// Get a 'random' attribute based on a seed. - /// - /// For fun and exploration we get the number from the BCS bytes. - /// This function demonstrates the way of getting a `u64` number - /// from a vector of bytes. - fun get_attribute(seed: &vector): Attribute { - let attr_values = ATTRIBUTE_VALUES; - let mut bcs_bytes = bcs::new(hash(*seed)); - let attr_idx = bcs::peel_u64(&mut bcs_bytes) % attr_values.length(); // get the index of the attribute - let attr_value = attr_values[attr_idx]; - - capy::create_attribute(ATTRIBUTE_NAME, attr_value) - } - - /// Get a URL for the box image. - /// TODO: specify capy.art here!!! - fun get_img_url(`type`: u8): Url { - let mut res = b"http://api.capy.art/box_"; - if (`type` == 99) { - res.append(b"premium"); - } else { - res.push_back(ASCII_OFFSET + `type`); - }; - - res.append(b".svg"); - - url::new_unsafe_from_bytes(res) - } - - /// Get a link to the gift on the capy.art. - fun get_link_url(id: &UID, `type`: u8): Url { - let mut res = b"http://capy.art/gifts/"; - res.append(sui::hex::encode(object::uid_to_bytes(id))); - res.append(b"?type="); - res.push_back(ASCII_OFFSET + `type`); - - url::new_unsafe_from_bytes(res) - } -} diff --git a/sui_programmability/examples/capy/sources/eden.move b/sui_programmability/examples/capy/sources/eden.move deleted file mode 100644 index 66e2caf14db3d..0000000000000 --- a/sui_programmability/examples/capy/sources/eden.move +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Simple permissionless application that stores two capys and -/// breeds them on request. -/// -/// Enables "get free capy" functionality. A single account can request -/// up to 2 capys; then it aborts. -module capy::eden { - use sui::dynamic_field as dfield; - - use capy::capy::{Self, Capy, CapyRegistry}; - - /// For when someone tries to breed more than 2 `Capy`s. - const EMaxBred: u64 = 0; - - /// A shared object containing 2 Capys for free breeding. - public struct Eden has key { - id: UID, - capy_one: Option, - capy_two: Option, - } - - #[allow(unused_function)] - fun init(ctx: &mut TxContext) { - sui::transfer::share_object(Eden { - id: object::new(ctx), - capy_one: option::none(), - capy_two: option::none() - }) - } - - /// Admin-only action to set 2 capys for breeding in the `Eden` object. - entry fun set(eden: &mut Eden, capy_one: Capy, capy_two: Capy) { - option::fill(&mut eden.capy_one, capy_one); - option::fill(&mut eden.capy_two, capy_two) - } - - #[allow(unused_function)] - /// Breed a "free" Capy using capys set in the `Eden` object. Can only be performed - /// twice. Aborts when trying to breed more than 2 times. - fun get_capy(eden: &mut Eden, reg: &mut CapyRegistry, ctx: &mut TxContext) { - let sender = tx_context::sender(ctx); - let total = if (dfield::exists_with_type(&eden.id, sender)) { - let total = dfield::remove(&mut eden.id, sender); - assert!(total != 2, EMaxBred); - total - } else { - 0 - }; - - dfield::add(&mut eden.id, sender, total + 1); - capy::breed_and_keep( - reg, - option::borrow_mut(&mut eden.capy_one), - option::borrow_mut(&mut eden.capy_two), - ctx - ) - } -} diff --git a/sui_programmability/examples/crypto/Move.toml b/sui_programmability/examples/crypto/Move.toml deleted file mode 100644 index 064d065078844..0000000000000 --- a/sui_programmability/examples/crypto/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "Crypto" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -crypto = "0x0" diff --git a/sui_programmability/examples/crypto/README.md b/sui_programmability/examples/crypto/README.md deleted file mode 100644 index 31d196a867032..0000000000000 --- a/sui_programmability/examples/crypto/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Crypto - -* ECDSA: A simple contract to do the following: -1) Hash a piece of data using keccak256, output an object with hashed data. -2) Recover a Secp256k1 signature to its public key, output an object with the public key. -3) Verify a Secp256k1 signature, produce an event for whether it is verified. - -* EC OPS: Examples for EC operations. diff --git a/sui_programmability/examples/crypto/sources/ec_ops.move b/sui_programmability/examples/crypto/sources/ec_ops.move deleted file mode 100644 index 9c5f1e30444ed..0000000000000 --- a/sui_programmability/examples/crypto/sources/ec_ops.move +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// Examples of cryptographic primitives that can be implemented in Move using group operations. -// -// Functions with the prefix "insecure" are here for testing, but should be called off-chain (probably implemented in -// other languages) to avoid leaking secrets. -module crypto::ec_ops { - - use sui::bls12381; - use sui::group_ops::Element; - use sui::group_ops; - use sui::hash::blake2b256; - #[test_only] - use std::hash::sha2_256; - #[test_only] - use sui::bcs; - #[test_only] - use sui::test_utils::assert_eq; - - const EInvalidLength: u64 = 0; - - const BLS12381_ORDER: vector = x"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"; - - //////////////////////////////////////// - ////// BLS signature verification ////// - - public fun bls_min_sig_verify(msg: &vector, pk: &Element, sig: &Element): bool { - let hashed_msg = bls12381::hash_to_g1(msg); - let lhs = bls12381::pairing(&hashed_msg, pk); - let rhs = bls12381::pairing(sig, &bls12381::g2_generator()); - group_ops::equal(&lhs, &rhs) - } - - #[test] - fun test_bls_min_sig_verify() { - let msg = x"0101010101"; - let pk = x"8df101606f91f3cad7f54b8aff0f0f64c41c482d9b9f9fe81d2b607bc5f611bdfa8017cf04b47b44b222c356ef555fbd11058c52c077f5a7ec6a15ccfd639fdc9bd47d005a111dd6cdb8c02fe49608df55a3c9822986ad0b86bdea3abfdfe464"; - let sig = x"908e345f2e2803cd941ae88c218c96194233c9053fa1bca52124787d3cca141c36429d7652435a820c72992d5eee6317"; - - let pk = bls12381::g2_from_bytes(&pk); - let sig= bls12381::g1_from_bytes(&sig); - assert!(bls_min_sig_verify(&msg, &pk, &sig), 0); - } - - - //////////////////////////////////////////////////////////////// - ////// Proof of plaintext equality of ElGamal encryptions ////// - - // An encryption of group element m under pk is (r*G, r*pk + m) for random r. - public struct ElGamalEncryption has drop, store { - ephemeral: Element, - ciphertext: Element, - } - - // The following is insecure since the secret key is small, but in practice it should be a random scalar. - #[test_only] - fun insecure_elgamal_key_gen(sk: u64): (Element, Element) { - let sk = bls12381::scalar_from_u64(sk); - let pk = bls12381::g1_mul(&sk, &bls12381::g1_generator()); - (sk, pk) - } - - // The following is insecure since the nonce is small, but in practice it should be a random scalar. - #[test_only] - fun insecure_elgamal_encrypt( - pk: &Element, - r: u64, - m: &Element - ): ElGamalEncryption { - let r = bls12381::scalar_from_u64(r); - let ephemeral = bls12381::g1_mul(&r, &bls12381::g1_generator()); - let pk_r = bls12381::g1_mul(&r, pk); - let ciphertext = bls12381::g1_add(m, &pk_r); - ElGamalEncryption { ephemeral, ciphertext } - } - - public fun elgamal_decrypt(sk: &Element, enc: &ElGamalEncryption): Element { - let pk_r = bls12381::g1_mul(sk, &enc.ephemeral); - bls12381::g1_sub(&enc.ciphertext, &pk_r) - } - - // Basic sigma protocol for proving equality of two ElGamal encryptions. - // See https://crypto.stackexchange.com/questions/30010/is-there-a-way-to-prove-equality-of-plaintext-that-was-encrypted-using-different - public struct EqualityProof has drop, store { - a1: Element, - a2: Element, - a3: Element, - z1: Element, - z2: Element, - } - - public fun fiat_shamir_challenge( - pk1: &Element, - pk2: &Element, - enc1: &ElGamalEncryption, - enc2: &ElGamalEncryption, - a1: &Element, - a2: &Element, - a3: &Element, - ): Element { - let mut to_hash = vector::empty(); - vector::append(&mut to_hash, *group_ops::bytes(pk1)); - vector::append(&mut to_hash, *group_ops::bytes(pk2)); - vector::append(&mut to_hash, *group_ops::bytes(&enc1.ephemeral)); - vector::append(&mut to_hash, *group_ops::bytes(&enc1.ciphertext)); - vector::append(&mut to_hash, *group_ops::bytes(&enc2.ephemeral)); - vector::append(&mut to_hash, *group_ops::bytes(&enc2.ciphertext)); - vector::append(&mut to_hash, *group_ops::bytes(a1)); - vector::append(&mut to_hash, *group_ops::bytes(a2)); - vector::append(&mut to_hash, *group_ops::bytes(a3)); - let mut hash = blake2b256(&to_hash); - // Make sure we are in the right field. Note that for security we only need the lower 128 bits. - *vector::borrow_mut(&mut hash, 0) = 0; - bls12381::scalar_from_bytes(&hash) - } - - // The following is insecure since the nonces are small, but in practice they should be random scalars. - #[test_only] - fun insecure_equility_prove( - pk1: &Element, - pk2: &Element, - enc1: &ElGamalEncryption, - enc2: &ElGamalEncryption, - sk1: &Element, - r1: u64, - r2: u64, - ): EqualityProof { - let b1 = bls12381::scalar_from_u64(r1); - let b2 = bls12381::scalar_from_u64(r1+123); - let r2 = bls12381::scalar_from_u64(r2); - - // a1 = b1*G (for proving knowledge of sk1) - let a1 = bls12381::g1_mul(&b1, &bls12381::g1_generator()); - // a2 = b2*g (for proving knowledge of r2) - let a2 = bls12381::g1_mul(&b2, &bls12381::g1_generator()); - let mut scalars = vector::singleton(b1); - vector::push_back(&mut scalars, bls12381::scalar_neg(&b2)); - let mut points = vector::singleton(enc1.ephemeral); - vector::push_back(&mut points, *pk2); - let a3 = bls12381::g1_multi_scalar_multiplication(&scalars, &points); - // RO challenge - let c = fiat_shamir_challenge(pk1, pk2, enc1, enc2, &a1, &a2, &a3); - // z1 = b1 + c*sk1 - let z1 = bls12381::scalar_add(&bls12381::scalar_mul(&c, sk1), &b1); - // z2 = b2 + c*r2 - let z2 = bls12381::scalar_add(&bls12381::scalar_mul(&c, &r2), &b2); - - EqualityProof { a1, a2, a3, z1, z2 } - } - - public fun equility_verify( - pk1: &Element, - pk2: &Element, - enc1: &ElGamalEncryption, - enc2: &ElGamalEncryption, - proof: &EqualityProof - ): bool { - let c = fiat_shamir_challenge(pk1, pk2, enc1, enc2, &proof.a1, &proof.a2, &proof.a3); - // Check if z1*G = a1 + c*pk1 - let lhs = bls12381::g1_mul(&proof.z1, &bls12381::g1_generator()); - let pk1_c = bls12381::g1_mul(&c, pk1); - let rhs = bls12381::g1_add(&proof.a1, &pk1_c); - if (!group_ops::equal(&lhs, &rhs)) { - return false - }; - // Check if z2*G = a2 + c*eph2 - let lhs = bls12381::g1_mul(&proof.z2, &bls12381::g1_generator()); - let eph2_c = bls12381::g1_mul(&c, &enc2.ephemeral); - let rhs = bls12381::g1_add(&proof.a2, &eph2_c); - if (!group_ops::equal(&lhs, &rhs)) { - return false - }; - // Check if a3 = c*(ct2 - ct1) + z1*eph1 - z2*pk2 - let mut scalars = vector::singleton(c); - vector::push_back(&mut scalars, bls12381::scalar_neg(&c)); - vector::push_back(&mut scalars, proof.z1); - vector::push_back(&mut scalars, bls12381::scalar_neg(&proof.z2)); - let mut points = vector::singleton(enc2.ciphertext); - vector::push_back(&mut points, enc1.ciphertext); - vector::push_back(&mut points, enc1.ephemeral); - vector::push_back(&mut points, *pk2); - let lhs = bls12381::g1_multi_scalar_multiplication(&scalars, &points); - if (!group_ops::equal(&lhs, &proof.a3)) { - return false - }; - - return true - } - - #[test] - fun test_elgamal_ops() { - // We have two parties. - let (sk1, pk1) = insecure_elgamal_key_gen(2110); - let (_, pk2) = insecure_elgamal_key_gen(1021); - // A sender wishes to send an encrypted message to pk1. - let m = bls12381::g1_mul(&bls12381::scalar_from_u64(5555), &bls12381::g1_generator()); - let enc1 = insecure_elgamal_encrypt(&pk1, 1234, &m); - // The first party decrypts the message. - let m1 = elgamal_decrypt(&sk1, &enc1); - assert_eq(m, m1); - // Now, the first party wishes to send an encrypted message to pk2. - let r2 = 4321; - let enc2 = insecure_elgamal_encrypt(&pk2, r2, &m); - // And to prove equality of the two encrypted messages. - let proof = insecure_equility_prove(&pk1, &pk2, &enc1, &enc2, &sk1, 8888, r2); - // Anyone can verify it. - assert!(equility_verify(&pk1, &pk2, &enc1, &enc2, &proof), 0); - - // Proving with an invalid witness should result in a failed verification. - let bad_r2 = 1111; - let proof = insecure_equility_prove(&pk1, &pk2, &enc1, &enc2, &sk1, 8888, bad_r2); - assert!(!equility_verify(&pk1, &pk2, &enc1, &enc2, &proof), 0); - } - - /////////////////////////////////////// - ////// tlock (an IBE decryption) ////// - - /// An encryption of 32 bytes message following https://eprint.iacr.org/2023/189.pdf. - public struct IbeEncryption has store, drop, copy { - u: Element, - v: vector, - w: vector, - } - - public fun ibe_encryption_from_bytes(bytes: &vector): IbeEncryption { - assert!(vector::length(bytes) == 96 + 32 + 32, 0); - let mut buffer = vector::empty(); - let mut i = 0; - while (i < 96) { - vector::push_back(&mut buffer, *vector::borrow(bytes, i)); - i = i + 1; - }; - let u = bls12381::g2_from_bytes(&buffer); - - let mut v = vector::empty(); - while (i < 96 + 32) { - vector::push_back(&mut v, *vector::borrow(bytes, i)); - i = i + 1; - }; - - let mut w = vector::empty(); - while (i < 96 + 32 + 32) { - vector::push_back(&mut w, *vector::borrow(bytes, i)); - i = i + 1; - }; - - IbeEncryption { u, v, w } - } - - // Encrypt a message 'm' for 'target'. Follows the algorithms of https://eprint.iacr.org/2023/189.pdf. - // Note that the algorithms in that paper use G2 for signatures, where the actual chain uses G1, thus - // the operations below are slightly different. - #[test_only] - fun insecure_ibe_encrypt(pk: &Element, target: &vector, m: &vector, sigma: &vector): IbeEncryption { - assert!(vector::length(sigma) == 32, 0); - - // pk_rho = e(H1(target), pk) - let target_hash = bls12381::hash_to_g1(target); - let pk_rho = bls12381::pairing(&target_hash, pk); - - // r = H3(sigma | m) as a scalar - assert!(vector::length(m) == vector::length(sigma), 0); - let mut to_hash = b"HASH3 - "; - vector::append(&mut to_hash, *sigma); - vector::append(&mut to_hash, *m); - let r = modulo_order(&blake2b256(&to_hash)); - let r = bls12381::scalar_from_bytes(&r); - - // U = r*g2 - let u = bls12381::g2_mul(&r, &bls12381::g2_generator()); - - // V = sigma xor H2(pk_rho^r) - let pk_rho_r = bls12381::gt_mul(&r, &pk_rho); - let mut to_hash = b"HASH2 - "; - vector::append(&mut to_hash, *group_ops::bytes(&pk_rho_r)); - let hash_pk_rho_r = blake2b256(&to_hash); - let mut v = vector::empty(); - let mut i = 0; - while (i < vector::length(sigma)) { - vector::push_back(&mut v, *vector::borrow(sigma, i) ^ *vector::borrow(&hash_pk_rho_r, i)); - i = i + 1; - }; - - // W = m xor H4(sigma) - let mut to_hash = b"HASH4 - "; - vector::append(&mut to_hash, *sigma); - let hash = blake2b256(&to_hash); - let mut w = vector::empty(); - let mut i = 0; - while (i < vector::length(m)) { - vector::push_back(&mut w, *vector::borrow(m, i) ^ *vector::borrow(&hash, i)); - i = i + 1; - }; - - IbeEncryption { u, v, w } - } - - // Decrypt an IBE encryption using a 'target_key'. - public fun ibe_decrypt(enc: IbeEncryption, target_key: &Element): Option> { - // sigma_prime = V xor H2(e(target_key, u)) - let e = bls12381::pairing(target_key, &enc.u); - let mut to_hash = b"HASH2 - "; - vector::append(&mut to_hash, *group_ops::bytes(&e)); - let hash = blake2b256(&to_hash); - let mut sigma_prime = vector::empty(); - let mut i = 0; - while (i < vector::length(&enc.v)) { - vector::push_back(&mut sigma_prime, *vector::borrow(&hash, i) ^ *vector::borrow(&enc.v, i)); - i = i + 1; - }; - - // m_prime = W xor H4(sigma_prime) - let mut to_hash = b"HASH4 - "; - vector::append(&mut to_hash, sigma_prime); - let hash = blake2b256(&to_hash); - let mut m_prime = vector::empty(); - let mut i = 0; - while (i < vector::length(&enc.w)) { - vector::push_back(&mut m_prime, *vector::borrow(&hash, i) ^ *vector::borrow(&enc.w, i)); - i = i + 1; - }; - - // r = H3(sigma_prime | m_prime) as a scalar (the paper has a typo) - let mut to_hash = b"HASH3 - "; - vector::append(&mut to_hash, sigma_prime); - vector::append(&mut to_hash, m_prime); - // If the encryption is generated correctly, this should always be a valid scalar (before the modulo). - // However since in the tests we create it insecurely, we make sure it is in the right range. - let r = modulo_order(&blake2b256(&to_hash)); - let r = bls12381::scalar_from_bytes(&r); - - // U ?= r*g2 - let g2r = bls12381::g2_mul(&r, &bls12381::g2_generator()); - if (group_ops::equal(&enc.u, &g2r)) { - option::some(m_prime) - } else { - option::none() - } - } - - // This test emulates drand based timelock encryption (using quicknet). - #[test] - fun test_ibe_decrypt_drand() { - // Retrieved using 'curl https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/info' - let round = 1234; - let pk_bytes = x"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"; - let pk = bls12381::g2_from_bytes(&pk_bytes); - let msg = x"0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"; - - // Derive the 'target' for the specific round (see drand_lib.move). - let mut round_bytes = bcs::to_bytes(&round); - vector::reverse(&mut round_bytes); - let target = sha2_256(round_bytes); - - // Retrieved with 'curl https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1234'. - let sig_bytes = x"a81d4aad15461a0a02b43da857be1d782a2232a3c7bb370a2763e95ce1f2628460b24de2cee7453cd12e43c197ea2f23"; - let target_key = bls12381::g1_from_bytes(&sig_bytes); - assert!(bls12381::bls12381_min_sig_verify(&sig_bytes, &pk_bytes, &target), 0); - - let enc = insecure_ibe_encrypt(&pk, &target, &msg, &x"A123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"); - let mut decrypted_msg = ibe_decrypt(enc, &target_key); - assert!(option::extract(&mut decrypted_msg) == msg, 0); - - // Test an example output that was generated by the Rust CLI. - let enc = x"b598e92e55ac1a2e78b61ce4a223c3c6b17db2dc4e5c807965649d882c71f05e1a7eac110e40c7b7faae4d556d6b418c03521e351504b371e91c1e7637292e4fb9f7ad4a8b6a1fecebd2b3208e18cab594b081d11cbfb1f15b7b18b4af6876fd796026a67def0b05222aadabcf86eaace0e708f469f491483f681e184f9178236f4e749635de4478f3bf44fb9264d35d6e83d58b3e5e686414b0953e99142a62"; - let enc = ibe_encryption_from_bytes(&enc); - let mut decrypted_msg = ibe_decrypt(enc, &target_key); - assert!(option::extract(&mut decrypted_msg) == msg, 0); - } - - /////////////////////////////////////////////////////////////////////////////////// - ////// Helper functions for converting 32 byte vectors to BLS12-381's order ////// - - // Returns x-ORDER if x >= ORDER, otherwise none. - fun try_substract(x: &vector): Option> { - assert!(vector::length(x) == 32, EInvalidLength); - let order = BLS12381_ORDER; - let mut c = vector::empty(); - let mut i = 0; - let mut carry: u8 = 0; - while (i < 32) { - let curr = 31 - i; - let b1 = *vector::borrow(x, curr); - let b2 = *vector::borrow(&order, curr); - let sum: u16 = (b2 as u16) + (carry as u16); - if (sum > (b1 as u16)) { - carry = 1; - let res = 0x100 + (b1 as u16) - sum; - vector::push_back(&mut c, (res as u8)); - } else { - carry = 0; - let res = (b1 as u16) - sum; - vector::push_back(&mut c, (res as u8)); - }; - i = i + 1; - }; - if (carry != 0) { - option::none() - } else { - vector::reverse(&mut c); - option::some(c) - } - } - - fun modulo_order(x: &vector): vector { - let mut res = *x; - // Since 2^256 < 3*ORDER, this loop won't run many times. - while (true) { - let minus_order = try_substract(&res); - if (option::is_none(&minus_order)) { - return res - }; - res = *option::borrow(&minus_order); - }; - res - } - - #[test] - fun test_try_substract_and_modulo() { - let smaller: vector = x"73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000"; - let res = try_substract(&smaller); - assert!(option::is_none(&res), 0); - - let bigger: vector = x"8c1258acd66282b7ccc627f7f65e27faac425bfd0001a40100000000fffffff5"; - let res = try_substract(&bigger); - assert!(option::is_some(&res), 0); - let bigger_minus_order = *option::borrow(&res); - let expected: vector = x"1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffff4"; - assert_eq(bigger_minus_order, expected); - - let larger: vector = x"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6"; - let expected: vector = x"1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffff4"; - let modulo = modulo_order(&larger); - assert!(modulo == expected, 0); - } - - // TODO: Groth16 proof verification -} diff --git a/sui_programmability/examples/crypto/sources/ecdsa.move b/sui_programmability/examples/crypto/sources/ecdsa.move deleted file mode 100644 index 9fdec34840e33..0000000000000 --- a/sui_programmability/examples/crypto/sources/ecdsa.move +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// A basic ECDSA utility contract to do the following: -// 1) Hash a piece of data using Keccak256, output an object with hashed data. -// 2) Recover a Secp256k1 signature to its public key, output an object with the public key. -// 3) Verify a Secp256k1 signature, produce an event for whether it is verified. -module crypto::ecdsa_k1 { - use sui::ecdsa_k1; - use sui::event; - - /// Event on whether the signature is verified - public struct VerifiedEvent has copy, drop { - is_verified: bool, - } - - /// Object that holds the output data - public struct Output has key, store { - id: UID, - value: vector - } - - /// Hash the data using Keccak256, output an object with the hash to recipient. - public entry fun keccak256(data: vector, recipient: address, ctx: &mut TxContext) { - let hashed = Output { - id: object::new(ctx), - value: sui::hash::keccak256(&data), - }; - // Transfer an output data object holding the hashed data to the recipient. - transfer::public_transfer(hashed, recipient) - } - - /// Recover the public key using the signature and message, assuming the signature was produced over the - /// Keccak256 hash of the message. Output an object with the recovered pubkey to recipient. - public entry fun ecrecover(signature: vector, msg: vector, recipient: address, ctx: &mut TxContext) { - let pubkey = Output { - id: object::new(ctx), - value: ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0), - }; - // Transfer an output data object holding the pubkey to the recipient. - transfer::public_transfer(pubkey, recipient) - } - - /// Recover the Ethereum address using the signature and message, assuming - /// the signature was produced over the Keccak256 hash of the message. - /// Output an object with the recovered address to recipient. - public entry fun ecrecover_to_eth_address(mut signature: vector, msg: vector, recipient: address, ctx: &mut TxContext) { - // Normalize the last byte of the signature to be 0 or 1. - let v = vector::borrow_mut(&mut signature, 64); - if (*v == 27) { - *v = 0; - } else if (*v == 28) { - *v = 1; - } else if (*v > 35) { - *v = (*v - 1) % 2; - }; - - // Ethereum signature is produced with Keccak256 hash of the message, so the last param is 0. - let pubkey = ecdsa_k1::secp256k1_ecrecover(&signature, &msg, 0); - let uncompressed = ecdsa_k1::decompress_pubkey(&pubkey); - - // Take the last 64 bytes of the uncompressed pubkey. - let mut uncompressed_64 = vector::empty(); - let mut i = 1; - while (i < 65) { - let value = vector::borrow(&uncompressed, i); - vector::push_back(&mut uncompressed_64, *value); - i = i + 1; - }; - - // Take the last 20 bytes of the hash of the 64-bytes uncompressed pubkey. - let hashed = sui::hash::keccak256(&uncompressed_64); - let mut addr = vector::empty(); - let mut i = 12; - while (i < 32) { - let value = vector::borrow(&hashed, i); - vector::push_back(&mut addr, *value); - i = i + 1; - }; - - let addr_object = Output { - id: object::new(ctx), - value: addr, - }; - - // Transfer an output data object holding the address to the recipient. - transfer::public_transfer(addr_object, recipient) - } - - /// Verified the secp256k1 signature using public key and message assuming Keccak was using when - /// signing. Emit an is_verified event of the verification result. - public entry fun secp256k1_verify(signature: vector, public_key: vector, msg: vector) { - event::emit(VerifiedEvent {is_verified: ecdsa_k1::secp256k1_verify(&signature, &public_key, &msg, 0)}); - } -} diff --git a/sui_programmability/examples/crypto/sources/groth16.move b/sui_programmability/examples/crypto/sources/groth16.move deleted file mode 100644 index 60ca08d0b5c66..0000000000000 --- a/sui_programmability/examples/crypto/sources/groth16.move +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// A verifier for the Groth16 zk-SNARK over the BLS12-381 construction. -// See https://eprint.iacr.org/2016/260.pdf for details. -module crypto::groth16 { - - use sui::bls12381; - use sui::group_ops::Element; - - const EInvalidNumberOfPublicInputs: u64 = 0; - - /// A Groth16 proof. - public struct Proof has drop { - a: Element, - b: Element, - c: Element, - } - - /// Create a new `Proof`. - public fun create_proof(a: Element, b: Element, c: Element): Proof { - Proof { - a, - b, - c, - } - } - - /// A Groth16 verifying key used to verify a zero-knowledge proof. - public struct VerifyingKey has store, drop { - alpha: Element, - beta: Element, - gamma: Element, - gamma_abc: vector>, - delta: Element, - } - - /// Create a new `VerifyingKey`. - public fun create_verifying_key( - alpha: Element, - beta: Element, - gamma: Element, - gamma_abc: vector>, - delta: Element): VerifyingKey { - VerifyingKey { - alpha, - beta, - gamma, - gamma_abc, - delta, - } - } - - /// A prepared verifying key. This makes verification faster than using the verifying key directly. - public struct PreparedVerifyingKey has store, drop { - alpha_beta: Element, - gamma_neg: Element, - gamma_abc: vector>, - delta_neg: Element, - } - - /// Create a PreparedVerifyingKey from a VerifyingKey. This only have to be done once. - public fun prepare(vk: VerifyingKey): PreparedVerifyingKey { - PreparedVerifyingKey { - alpha_beta: bls12381::pairing(&vk.alpha, &vk.beta), - gamma_neg: bls12381::g2_neg(&vk.gamma), - gamma_abc: vk.gamma_abc, - delta_neg: bls12381::g2_neg(&vk.delta), - } - } - - fun prepare_inputs(vk_gamma_abc: &vector>, public_inputs: &vector>): Element { - let length = public_inputs.length(); - assert!(length + 1 == vk_gamma_abc.length(), EInvalidNumberOfPublicInputs); - - let mut output = vk_gamma_abc[0]; - let mut i = 0; - while (i < length) { - output = bls12381::g1_add(&output, &bls12381::g1_mul(&public_inputs[i], &vk_gamma_abc[i + 1])); - i = i + 1; - }; - output - } - - /// Verify a Groth16 proof with some public inputs and a verifying key. - public fun verify(pvk: &PreparedVerifyingKey, proof: &Proof, public_inputs: &vector>): bool { - let prepared_inputs = prepare_inputs(&pvk.gamma_abc, public_inputs); - let mut lhs = bls12381::pairing(&proof.a, &proof.b); - lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&prepared_inputs, &pvk.gamma_neg)); - lhs = bls12381::gt_add(&lhs, &bls12381::pairing(&proof.c, &pvk.delta_neg)); - lhs == pvk.alpha_beta - } - - #[test] - fun test_verification() { - let vk = create_verifying_key( - bls12381::g1_from_bytes(&x"b58cfc3b0f43d98e7dbe865af692577d52813cb62ef3c355215ec3be2a0355a1ae5da76dd3e626f8a60de1f4a8138dee"), - bls12381::g2_from_bytes(&x"9047b42915b32ef9dffe3acc0121a1450416e7f9791159f165ab0729d744da3ed82cd4822ca1d7fef35147cfd620b59b0ca09db7dff43aab6c71635ba8f86a83f058e9922e5cdacbe21d0e5e855cf1e776a61b272c12272fe526f5ba3b48d579"), - bls12381::g2_from_bytes(&x"ad7c5a6cefcae53a3fbae72662c7c04a2f8e1892cb83615a02b32c31247172b7f317489b84e72f14acaf4f3e9ed18141157c6c1464bf15d957227f75a3c550d6d27f295b41a753340c6eec47b471b2cb8664c84f3e9b725325d3fb8afc6b56d0"), - vector[ - bls12381::g1_from_bytes(&x"b2c9c61ccc28e913284a47c34e60d487869ff423dd574db080d35844f9eddd2b2967141b588a35fa82a278ce39ae6b1a"), - bls12381::g1_from_bytes(&x"9026ae12d58d203b4fc5dfad4968cbf51e43632ed1a05afdcb2e380ee552b036fbefc7780afe9675bcb60201a2421b2c") - ], - bls12381::g2_from_bytes(&x"b1294927d02f8e86ac57c3b832f4ecf5e03230445a9a785ac8d25cf968f48cca8881d0c439c7e8870b66567cf611da0c1734316632f39d3125c8cecca76a8661db91cbfae217547ea1fc078a24a1a31555a46765011411094ec649d42914e2f5"), - ); - - let public_inputs = vector[bls12381::scalar_from_bytes(&x"46722abc81a82d01ac89c138aa01a8223cb239ceb1f02cdaad7e1815eb997ca6")]; - - let proof = create_proof( - bls12381::g1_from_bytes(&x"9913bdcabdff2cf1e7dea1673c5edba5ed6435df2f2a58d6d9e624609922bfa3976a98d991db333812bf6290a590afaa"), - bls12381::g2_from_bytes(&x"b0265b35af5069593ee88626cf3ba9a0f07699510a25aec3a27048792ab83b3467d6b814d1c09c412c4dcd7656582e6607b72915081c82794ccedf643c27abace5b23a442079d8dcbd0d68dd697b8e0b699a1925a5f2c77f5237efbbbeda3bd0"), - bls12381::g1_from_bytes(&x"b1237cf48ca7aa98507e826aac336b9e24f14133de1923fffac602a1203b795b3037c4c94e7246bacee7b2757ae912e5"), - ); - - assert!(verify(&vk.prepare(), &proof, &public_inputs), 0); - } -} diff --git a/sui_programmability/examples/defi/Move.toml b/sui_programmability/examples/defi/Move.toml deleted file mode 100644 index f30bf2bfbb1e6..0000000000000 --- a/sui_programmability/examples/defi/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "DeFi" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -defi = "0x0" diff --git a/sui_programmability/examples/defi/README.md b/sui_programmability/examples/defi/README.md deleted file mode 100644 index b0a2a3121a9c4..0000000000000 --- a/sui_programmability/examples/defi/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# DeFi - -* FlashLoan: a flash loan is a loan that must be initiated and repaid during the same transaction. This implementation works for any currency type, and is a good illustration of the power of Move [abilities](https://move-language.github.io/move/abilities.html) and the "hot potato" design pattern. -* Escrow: an atomic swap leveraging an escrow agent that is trusted for liveness, but not safety (i.e., the agent cannot steal the goods being swapped). -* Uniswap 1.0-style DEX (coming in future). diff --git a/sui_programmability/examples/defi/sources/escrow.move b/sui_programmability/examples/defi/sources/escrow.move deleted file mode 100644 index fb709b8f0b175..0000000000000 --- a/sui_programmability/examples/defi/sources/escrow.move +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// An escrow for atomic swap of objects that trusts a third party for liveness, but not safety. -module defi::escrow { - /// An object held in escrow - public struct EscrowedObj has key, store { - id: UID, - /// owner of the escrowed object - sender: address, - /// intended recipient of the escrowed object - recipient: address, - /// ID of the object `sender` wants in exchange - // TODO: this is probably a bad idea if the object is mutable. - // that can be fixed by asking for an additional approval - // from `sender`, but let's keep it simple for now. - exchange_for: ID, - /// the escrowed object - escrowed: T, - } - - // Error codes - /// The `sender` and `recipient` of the two escrowed objects do not match - const EMismatchedSenderRecipient: u64 = 0; - /// The `exchange_for` fields of the two escrowed objects do not match - const EMismatchedExchangeObject: u64 = 1; - - /// Create an escrow for exchanging goods with - /// `counterparty`, mediated by a `third_party` - /// that is trusted for liveness - public fun create( - recipient: address, - third_party: address, - exchange_for: ID, - escrowed: T, - ctx: &mut TxContext - ) { - let sender = ctx.sender(); - let id = object::new(ctx); - // escrow the object with the trusted third party - transfer::public_transfer( - EscrowedObj { - id, sender, recipient, exchange_for, escrowed - }, - third_party - ); - } - - /// Trusted third party can swap compatible objects - public entry fun swap( - obj1: EscrowedObj, - obj2: EscrowedObj, - ) { - let EscrowedObj { - id: id1, - sender: sender1, - recipient: recipient1, - exchange_for: exchange_for1, - escrowed: escrowed1, - } = obj1; - let EscrowedObj { - id: id2, - sender: sender2, - recipient: recipient2, - exchange_for: exchange_for2, - escrowed: escrowed2, - } = obj2; - id1.delete(); - id2.delete(); - // check sender/recipient compatibility - assert!(&sender1 == &recipient2, EMismatchedSenderRecipient); - assert!(&sender2 == &recipient1, EMismatchedSenderRecipient); - // check object ID compatibility - assert!(object::id(&escrowed1) == exchange_for2, EMismatchedExchangeObject); - assert!(object::id(&escrowed2) == exchange_for1, EMismatchedExchangeObject); - // everything matches. do the swap! - transfer::public_transfer(escrowed1, sender2); - transfer::public_transfer(escrowed2, sender1) - } - - /// Trusted third party can always return an escrowed object to its original owner - public entry fun return_to_sender( - obj: EscrowedObj, - ) { - let EscrowedObj { - id, sender, recipient: _, exchange_for: _, escrowed - } = obj; - id.delete(); - transfer::public_transfer(escrowed, sender) - } -} diff --git a/sui_programmability/examples/defi/sources/flash_lender.move b/sui_programmability/examples/defi/sources/flash_lender.move deleted file mode 100644 index 8f4aa2b4e5e54..0000000000000 --- a/sui_programmability/examples/defi/sources/flash_lender.move +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// A flash loan that works for any Coin type -module defi::flash_lender { - use sui::{coin::{Self, Coin}, balance::Balance}; - - - /// A shared object offering flash loans to any buyer willing to pay `fee`. - public struct FlashLender has key { - id: UID, - /// Coins available to be lent to prospective borrowers - to_lend: Balance, - /// Number of `Coin`'s that will be charged for the loan. - /// In practice, this would probably be a percentage, but - /// we use a flat fee here for simplicity. - fee: u64, - } - - /// A "hot potato" struct recording the number of `Coin`'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. - public struct Receipt { - /// 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 `AdminCap` per lender exists. - public struct AdminCap has key, store { - id: UID, - 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 `AdminCap` - 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(to_lend: Balance, fee: u64, ctx: &mut TxContext): AdminCap { - let id = object::new(ctx); - let flash_lender_id = id.to_inner(); - let flash_lender = FlashLender { id, to_lend, fee }; - // make the `FlashLender` a shared object so anyone can request loans - transfer::share_object(flash_lender); - - // give the creator admin permissions - AdminCap { id: object::new(ctx), flash_lender_id } - } - - /// Same as `new`, but transfer `AdminCap` to the transaction sender - public entry fun create(to_lend: Coin, fee: u64, ctx: &mut TxContext) { - let balance = to_lend.into_balance(); - let admin_cap = new(balance, fee, ctx); - - transfer::public_transfer(admin_cap, ctx.sender()) - } - - // === Core functionality: requesting a loan and repaying it === - - /// Request a loan of `amount` from `lender`. The returned `Receipt` "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( - self: &mut FlashLender, amount: u64, ctx: &mut TxContext - ): (Coin, Receipt) { - let to_lend = &mut self.to_lend; - assert!(to_lend.value() >= 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(self: &mut FlashLender, payment: Coin, receipt: Receipt) { - let Receipt { flash_lender_id, repay_amount } = receipt; - assert!(object::id(self) == flash_lender_id, ERepayToWrongLender); - assert!(payment.value() == repay_amount, EInvalidRepaymentAmount); - - coin::put(&mut self.to_lend, payment) - } - - // === Admin-only functionality === - - /// Allow admin for `self` to withdraw funds. - public fun withdraw( - self: &mut FlashLender, - admin_cap: &AdminCap, - amount: u64, - ctx: &mut TxContext - ): Coin { - // only the holder of the `AdminCap` for `self` can withdraw funds - check_admin(self, admin_cap); - - let to_lend = &mut self.to_lend; - assert!(to_lend.value() >= amount, EWithdrawTooLarge); - coin::take(to_lend, amount, ctx) - } - - /// Allow admin to add more funds to `self` - public entry fun deposit( - self: &mut FlashLender, admin_cap: &AdminCap, coin: Coin - ) { - // only the holder of the `AdminCap` 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( - self: &mut FlashLender, admin_cap: &AdminCap, new_fee: u64 - ) { - // only the holder of the `AdminCap` for `self` can update the fee - check_admin(self, admin_cap); - - self.fee = new_fee - } - - fun check_admin(self: &FlashLender, admin_cap: &AdminCap) { - assert!(object::borrow_id(self) == &admin_cap.flash_lender_id, EAdminOnly); - } - - // === Reads === - - /// Return the current fee for `self` - public fun fee(self: &FlashLender): u64 { - self.fee - } - - /// Return the maximum amount available for borrowing - public fun max_loan(self: &FlashLender): u64 { - self.to_lend.value() - } - - /// Return the amount that the holder of `self` must repay - public fun repay_amount(self: &Receipt): u64 { - self.repay_amount - } - - /// Return the amount that the holder of `self` must repay - public fun flash_lender_id(self: &Receipt): ID { - self.flash_lender_id - } -} diff --git a/sui_programmability/examples/defi/sources/pool.move b/sui_programmability/examples/defi/sources/pool.move deleted file mode 100644 index 0991dacedeaaf..0000000000000 --- a/sui_programmability/examples/defi/sources/pool.move +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Example implementation of a liquidity Pool for Sui. -/// -/// - Only module publisher can create new Pools. -/// - For simplicity's sake all swaps are done with SUI coin. -/// Generalizing to swaps between non-SUI coin types requires a few more generics, but is otherwise straightforward -/// - Fees are customizable per Pool. -/// - Max stored value for both tokens is: U64_MAX / 10_000 -/// -/// To publish a new pool, a type is required. Eg: -/// ``` -/// module me::my_pool { -/// use defi::pool; -/// use sui::coin::Coin; -/// use sui::sui::SUI; -/// use sui::tx_context::TxContext; -/// -/// struct POOL_TEAM has drop {} -/// -/// entry fun create_pool( -/// token: Coin, -/// sui: Coin, -/// fee_percent: u64, -/// ctx: &mut TxContext -/// ) { -/// pool::create_pool(POOL_TEAM {}, token, sui, fee_percent, ctx) -/// } -/// } -/// ``` -/// -/// This solution is rather simple and is based on the example from the Move repo: -/// https://github.com/move-language/move/blob/main/language/documentation/examples/experimental/coin-swap/sources/CoinSwap.move -module defi::pool { - use sui::coin::{Self, Coin}; - use sui::balance::{Self, Supply, Balance}; - use sui::sui::SUI; - - /// For when supplied Coin is zero. - const EZeroAmount: u64 = 0; - - /// For when pool fee is set incorrectly. - /// Allowed values are: [0-10000). - const EWrongFee: u64 = 1; - - /// For when someone tries to swap in an empty pool. - const EReservesEmpty: u64 = 2; - - /// For when someone attempts to add more liquidity than u128 Math allows. - const EPoolFull: u64 = 4; - - /// The integer scaling setting for fees calculation. - const FEE_SCALING: u128 = 10000; - - /// The max value that can be held in one of the Balances of - /// a Pool. U64 MAX / FEE_SCALING - const MAX_POOL_VALUE: u64 = { - 18446744073709551615 / 10000 - }; - - /// The Pool token that will be used to mark the pool share - /// of a liquidity provider. The first type parameter stands - /// for the witness type of a pool. The seconds is for the - /// coin held in the pool. - public struct LSP has drop {} - - /// The pool with exchange. - /// - /// - `fee_percent` should be in the range: [0-10000), meaning - /// that 10000 is 100% and 1 is 0.1% - public struct Pool has key { - id: UID, - sui: Balance, - token: Balance, - lsp_supply: Supply>, - /// Fee Percent is denominated in basis points. - fee_percent: u64 - } - - #[allow(unused_function)] - /// Module initializer is empty - to publish a new Pool one has - /// to create a type which will mark LSPs. - fun init(_: &mut TxContext) {} - - /// Create new `Pool` for token `T`. Each Pool holds a `Coin` - /// and a `Coin`. Swaps are available in both directions. - /// - /// Share is calculated based on Uniswap's constant product formula: - /// liquidity = sqrt( X * Y ) - public fun create_pool( - _: P, - token: Coin, - sui: Coin, - fee_percent: u64, - ctx: &mut TxContext - ): Coin> { - let sui_amt = sui.value(); - let tok_amt = token.value(); - - assert!(sui_amt > 0 && tok_amt > 0, EZeroAmount); - assert!(sui_amt < MAX_POOL_VALUE && tok_amt < MAX_POOL_VALUE, EPoolFull); - assert!(fee_percent >= 0 && fee_percent < 10000, EWrongFee); - - // Initial share of LSP is the sqrt(a) * sqrt(b) - let share = sui_amt.sqrt() * tok_amt.sqrt(); - let mut lsp_supply = balance::create_supply(LSP {}); - let lsp = lsp_supply.increase_supply(share); - - transfer::share_object(Pool { - id: object::new(ctx), - token: token.into_balance(), - sui: sui.into_balance(), - lsp_supply, - fee_percent - }); - - coin::from_balance(lsp, ctx) - } - - - /// Entrypoint for the `swap_sui` method. Sends swapped token - /// to sender. - entry fun swap_sui_( - pool: &mut Pool, sui: Coin, ctx: &mut TxContext - ) { - transfer::public_transfer( - swap_sui(pool, sui, ctx), - ctx.sender() - ) - } - - /// Swap `Coin` for the `Coin`. - /// Returns Coin. - public fun swap_sui( - pool: &mut Pool, sui: Coin, ctx: &mut TxContext - ): Coin { - assert!(sui.value() > 0, EZeroAmount); - - let sui_balance = sui.into_balance(); - - // Calculate the output amount - fee - let (sui_reserve, token_reserve, _) = get_amounts(pool); - - assert!(sui_reserve > 0 && token_reserve > 0, EReservesEmpty); - - let output_amount = get_input_price( - sui_balance.value(), - sui_reserve, - token_reserve, - pool.fee_percent - ); - - pool.sui.join(sui_balance); - coin::take(&mut pool.token, output_amount, ctx) - } - - /// Entry point for the `swap_token` method. Sends swapped SUI - /// to the sender. - entry fun swap_token_( - pool: &mut Pool, token: Coin, ctx: &mut TxContext - ) { - transfer::public_transfer( - swap_token(pool, token, ctx), - ctx.sender() - ) - } - - /// Swap `Coin` for the `Coin`. - /// Returns the swapped `Coin`. - public fun swap_token( - pool: &mut Pool, token: Coin, ctx: &mut TxContext - ): Coin { - assert!(token.value() > 0, EZeroAmount); - - let tok_balance = token.into_balance(); - let (sui_reserve, token_reserve, _) = get_amounts(pool); - - assert!(sui_reserve > 0 && token_reserve > 0, EReservesEmpty); - - let output_amount = get_input_price( - tok_balance.value(), - token_reserve, - sui_reserve, - pool.fee_percent - ); - - pool.token.join(tok_balance); - coin::take(&mut pool.sui, output_amount, ctx) - } - - /// Entrypoint for the `add_liquidity` method. Sends `Coin` to - /// the transaction sender. - entry fun add_liquidity_( - pool: &mut Pool, sui: Coin, token: Coin, ctx: &mut TxContext - ) { - transfer::public_transfer( - add_liquidity(pool, sui, token, ctx), - ctx.sender() - ); - } - - /// Add liquidity to the `Pool`. Sender needs to provide both - /// `Coin` and `Coin`, and in exchange he gets `Coin` - - /// liquidity provider tokens. - public fun add_liquidity( - pool: &mut Pool, sui: Coin, token: Coin, ctx: &mut TxContext - ): Coin> { - assert!(sui.value() > 0, EZeroAmount); - assert!(sui.value() > 0, EZeroAmount); - - let sui_balance = sui.into_balance(); - let tok_balance = token.into_balance(); - - let (sui_amount, tok_amount, lsp_supply) = get_amounts(pool); - - let sui_added = sui_balance.value(); - let tok_added = tok_balance.value(); - let share_minted = - ((sui_added * lsp_supply) / sui_amount).min((tok_added * lsp_supply) / tok_amount); - - let sui_amt = pool.sui.join(sui_balance); - let tok_amt = pool.token.join(tok_balance); - - assert!(sui_amt < MAX_POOL_VALUE, EPoolFull); - assert!(tok_amt < MAX_POOL_VALUE, EPoolFull); - - let balance = pool.lsp_supply.increase_supply(share_minted); - coin::from_balance(balance, ctx) - } - - /// Entrypoint for the `remove_liquidity` method. Transfers - /// withdrawn assets to the sender. - entry fun remove_liquidity_( - pool: &mut Pool, - lsp: Coin>, - ctx: &mut TxContext - ) { - let (sui, token) = remove_liquidity(pool, lsp, ctx); - let sender = ctx.sender(); - - transfer::public_transfer(sui, sender); - transfer::public_transfer(token, sender); - } - - /// Remove liquidity from the `Pool` by burning `Coin`. - /// Returns `Coin` and `Coin`. - public fun remove_liquidity( - pool: &mut Pool, - lsp: Coin>, - ctx: &mut TxContext - ): (Coin, Coin) { - let lsp_amount = lsp.value(); - - // If there's a non-empty LSP, we can - assert!(lsp_amount > 0, EZeroAmount); - - let (sui_amt, tok_amt, lsp_supply) = get_amounts(pool); - let sui_removed = (sui_amt * lsp_amount) / lsp_supply; - let tok_removed = (tok_amt * lsp_amount) / lsp_supply; - - pool.lsp_supply.decrease_supply(lsp.into_balance()); - - ( - coin::take(&mut pool.sui, sui_removed, ctx), - coin::take(&mut pool.token, tok_removed, ctx) - ) - } - - /// Public getter for the price of SUI in token T. - /// - How much SUI one will get if they send `to_sell` amount of T; - public fun sui_price(pool: &Pool, to_sell: u64): u64 { - let (sui_amt, tok_amt, _) = get_amounts(pool); - get_input_price(to_sell, tok_amt, sui_amt, pool.fee_percent) - } - - /// Public getter for the price of token T in SUI. - /// - How much T one will get if they send `to_sell` amount of SUI; - public fun token_price(pool: &Pool, to_sell: u64): u64 { - let (sui_amt, tok_amt, _) = get_amounts(pool); - get_input_price(to_sell, sui_amt, tok_amt, pool.fee_percent) - } - - - /// Get most used values in a handy way: - /// - amount of SUI - /// - amount of token - /// - total supply of LSP - public fun get_amounts(pool: &Pool): (u64, u64, u64) { - ( - pool.sui.value(), - pool.token.value(), - pool.lsp_supply.supply_value() - ) - } - - /// Calculate the output amount minus the fee - 0.3% - public fun get_input_price( - input_amount: u64, input_reserve: u64, output_reserve: u64, fee_percent: u64 - ): u64 { - // up casts - let ( - input_amount, - input_reserve, - output_reserve, - fee_percent - ) = ( - (input_amount as u128), - (input_reserve as u128), - (output_reserve as u128), - (fee_percent as u128) - ); - - let input_amount_with_fee = input_amount * (FEE_SCALING - fee_percent); - let numerator = input_amount_with_fee * output_reserve; - let denominator = (input_reserve * FEE_SCALING) + input_amount_with_fee; - - (numerator / denominator as u64) - } - - #[test_only] - public fun init_for_testing(ctx: &mut TxContext) { - init(ctx) - } -} - -#[test_only] -/// Tests for the pool module. -/// They are sequential and based on top of each other. -/// ``` -/// * - test_init_pool -/// | +-- test_creation -/// | +-- test_swap_sui -/// | +-- test_swap_tok -/// | +-- test_withdraw_almost_all -/// | +-- test_withdraw_all -/// ``` -module defi::pool_tests { - use sui::sui::SUI; - use sui::coin::{Coin, mint_for_testing as mint}; - use sui::test_scenario::{Self as test, Scenario, next_tx, ctx}; - use defi::pool::{Self, Pool, LSP}; - use sui::test_utils; - - /// Gonna be our test token. - public struct BEEP {} - - /// A witness type for the pool creation; - /// The pool provider's identifier. - public struct POOLEY has drop {} - - const SUI_AMT: u64 = 1000000000; - const BEEP_AMT: u64 = 1000000; - - // Tests section - #[test] fun test_init_pool() { - let mut scenario = scenario(); - test_init_pool_(&mut scenario); - scenario.end(); - } - #[test] fun test_add_liquidity() { - let mut scenario = scenario(); - test_add_liquidity_(&mut scenario); - scenario.end(); - } - #[test] fun test_swap_sui() { - let mut scenario = scenario(); - test_swap_sui_(&mut scenario); - scenario.end(); - } - #[test] fun test_swap_tok() { - let mut scenario = scenario(); - test_swap_tok_(&mut scenario); - scenario.end(); - } - #[test] fun test_withdraw_almost_all() { - let mut scenario = scenario(); - test_withdraw_almost_all_(&mut scenario); - scenario.end(); - } - #[test] fun test_withdraw_all() { - let mut scenario = scenario(); - test_withdraw_all_(&mut scenario); - scenario.end(); - } - - // Non-sequential tests - #[test] fun test_math() { - let mut scenario = scenario(); - test_math_(&mut scenario); - scenario.end(); - } - - #[test_only] - fun burn(x: Coin): u64 { - let value = x.value(); - test_utils::destroy(x); - value - } - - /// Init a Pool with a 1_000_000 BEEP and 1_000_000_000 SUI; - /// Set the ratio BEEP : SUI = 1 : 1000. - /// Set LSP token amount to 1000; - fun test_init_pool_(test: &mut Scenario) { - let (owner, _) = people(); - - test.next_tx(owner); - { - pool::init_for_testing(ctx(test)); - }; - - test.next_tx(owner); - { - let lsp = pool::create_pool( - POOLEY {}, - mint(BEEP_AMT, ctx(test)), - mint(SUI_AMT, ctx(test)), - 3, - ctx(test) - ); - - assert!(burn(lsp) == 31622000, 0); - }; - - test.next_tx(owner); - { - let pool = test.take_shared>(); - let (amt_sui, amt_tok, lsp_supply) = pool::get_amounts(&pool); - - assert!(lsp_supply == 31622000, 0); - assert!(amt_sui == SUI_AMT, 0); - assert!(amt_tok == BEEP_AMT, 0); - - test::return_shared(pool) - }; - } - - /// Expect LP tokens to double in supply when the same values passed - fun test_add_liquidity_(test: &mut Scenario) { - test_init_pool_(test); - - let (_, theguy) = people(); - - test.next_tx(theguy); - { - let mut pool = test.take_shared>(); - let pool_mut = &mut pool; - let (amt_sui, amt_tok, lsp_supply) = pool::get_amounts(pool_mut); - - let lsp_tokens = pool::add_liquidity( - pool_mut, - mint(amt_sui, ctx(test)), - mint(amt_tok, ctx(test)), - ctx(test) - ); - - assert!(burn(lsp_tokens) == lsp_supply, 1); - - test::return_shared(pool) - }; - } - - /// The other guy tries to exchange 5_000_000 sui for ~ 5000 BEEP, - /// minus the commission that is paid to the pool. - fun test_swap_sui_(test: &mut Scenario) { - test_init_pool_(test); - - let (_, the_guy) = people(); - - test.next_tx(the_guy); - { - let mut pool = test.take_shared>(); - let pool_mut = &mut pool; - - let token = pool::swap_sui(pool_mut, mint(5000000, ctx(test)), ctx(test)); - - // Check the value of the coin received by the guy. - // Due to rounding problem the value is not precise - // (works better on larger numbers). - assert!(burn(token) > 4950, 1); - - test::return_shared(pool); - }; - } - - /// The owner swaps back BEEP for SUI and expects an increase in price. - /// The sent amount of BEEP is 1000, initial price was 1 BEEP : 1000 SUI; - fun test_swap_tok_(test: &mut Scenario) { - test_swap_sui_(test); - - let (owner, _) = people(); - - test.next_tx(owner); - { - let mut pool = test.take_shared>(); - let pool_mut = &mut pool; - - let sui = pool::swap_token(pool_mut, mint(1000, ctx(test)), ctx(test)); - - // Actual win is 1005971, which is ~ 0.6% profit - assert!(burn(sui) > 1000000u64, 2); - - test::return_shared(pool); - }; - } - - /// Withdraw (MAX_LIQUIDITY - 1) from the pool - fun test_withdraw_almost_all_(test: &mut Scenario) { - test_swap_tok_(test); - - let (owner, _) = people(); - - // someone tries to pass (MINTED_LSP - 1) and hopes there will be just 1 BEEP - test.next_tx(owner); - { - let lsp = mint>(31622000 - 1, ctx(test)); - let mut pool = test.take_shared>(); - let pool_mut = &mut pool; - - let (sui, tok) = pool::remove_liquidity(pool_mut, lsp, ctx(test)); - let (sui_reserve, tok_reserve, lsp_supply) = pool::get_amounts(pool_mut); - - assert!(lsp_supply == 1, 3); - assert!(tok_reserve > 0, 3); // actually 1 BEEP is left - assert!(sui_reserve > 0, 3); - - burn(sui); - burn(tok); - - test::return_shared(pool); - } - } - - /// The owner tries to withdraw all liquidity from the pool. - fun test_withdraw_all_(test: &mut Scenario) { - test_swap_tok_(test); - - let (owner, _) = people(); - - next_tx(test, owner); - { - let lsp = mint>(31622000, ctx(test)); - let mut pool = test.take_shared>(); - let pool_mut = &mut pool; - - let (sui, tok) = pool::remove_liquidity(pool_mut, lsp, ctx(test)); - let (sui_reserve, tok_reserve, lsp_supply) = pool::get_amounts(pool_mut); - - assert!(sui_reserve == 0, 3); - assert!(tok_reserve == 0, 3); - assert!(lsp_supply == 0, 3); - - // make sure that withdrawn assets - assert!(burn(sui) > 1000000000, 3); - assert!(burn(tok) < 1000000, 3); - - test::return_shared(pool); - }; - } - - /// This just tests the math. - fun test_math_(_: &mut Scenario) { - let u64_max = 18446744073709551615; - let max_val = u64_max / 10000; - - // Try small values - assert!(pool::get_input_price(10, 1000, 1000, 0) == 9, 0); - - // Even with 0 commission there's this small loss of 1 - assert!(pool::get_input_price(10000, max_val, max_val, 0) == 9999, 0); - assert!(pool::get_input_price(1000, max_val, max_val, 0) == 999, 0); - assert!(pool::get_input_price(100, max_val, max_val, 0) == 99, 0); - } - - // utilities - fun scenario(): Scenario { test::begin(@0x1) } - - fun people(): (address, address) { (@0xBEEF, @0x1337) } -} diff --git a/sui_programmability/examples/defi/sources/shared_escrow.move b/sui_programmability/examples/defi/sources/shared_escrow.move deleted file mode 100644 index e2548e3834221..0000000000000 --- a/sui_programmability/examples/defi/sources/shared_escrow.move +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// An escrow for atomic swap of objects without a trusted third party -module defi::shared_escrow { - /// An object held in escrow - public struct EscrowedObj has key, store { - id: UID, - /// owner of the escrowed object - creator: address, - /// intended recipient of the escrowed object - recipient: address, - /// ID of the object `creator` wants in exchange - exchange_for: ID, - /// the escrowed object - escrowed: Option, - } - - // Error codes - /// An attempt to cancel escrow by a different user than the owner - const EWrongOwner: u64 = 0; - /// Exchange by a different user than the `recipient` of the escrowed object - const EWrongRecipient: u64 = 1; - /// Exchange with a different item than the `exchange_for` field - const EWrongExchangeObject: u64 = 2; - /// The escrow has already been exchanged or cancelled - const EAlreadyExchangedOrCancelled: u64 = 3; - - /// Create an escrow for exchanging goods with counterparty - public fun create( - recipient: address, - exchange_for: ID, - escrowed_item: T, - ctx: &mut TxContext - ) { - let creator = ctx.sender(); - let id = object::new(ctx); - let escrowed = option::some(escrowed_item); - transfer::public_share_object( - EscrowedObj { - id, creator, recipient, exchange_for, escrowed - } - ); - } - - /// The `recipient` of the escrow can exchange `obj` with the escrowed item - public entry fun exchange( - obj: ExchangeForT, - escrow: &mut EscrowedObj, - ctx: &TxContext - ) { - assert!(option::is_some(&escrow.escrowed), EAlreadyExchangedOrCancelled); - let escrowed_item = option::extract(&mut escrow.escrowed); - assert!(&ctx.sender() == &escrow.recipient, EWrongRecipient); - assert!(object::borrow_id(&obj) == &escrow.exchange_for, EWrongExchangeObject); - // everything matches. do the swap! - transfer::public_transfer(escrowed_item, ctx.sender()); - transfer::public_transfer(obj, escrow.creator); - } - - /// The `creator` can cancel the escrow and get back the escrowed item - public entry fun cancel( - escrow: &mut EscrowedObj, - ctx: &TxContext - ) { - assert!(&ctx.sender() == &escrow.creator, EWrongOwner); - assert!(option::is_some(&escrow.escrowed), EAlreadyExchangedOrCancelled); - transfer::public_transfer(option::extract(&mut escrow.escrowed), escrow.creator); - } -} diff --git a/sui_programmability/examples/defi/sources/subscription.move b/sui_programmability/examples/defi/sources/subscription.move deleted file mode 100644 index b27bc376f86ae..0000000000000 --- a/sui_programmability/examples/defi/sources/subscription.move +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This example introduces a `Subscription` type - a `Capability`-like object -/// with a limited number of uses. Once all of them are used, the subscription can -/// be renewed or destroyed. -/// -/// Design of this module implies that an application will implement the subscription -/// interface using a `Witness` pattern, while some features are given by default -/// and do not require implementation. -/// -/// -/// The problem that illustrates usage of this module might be common in Sui. To -/// get to it, we need to provide some background: -/// -/// - Shared object transactions in Sui require consensus; and for each shared -/// object there's a sequence of transactions which use / reference it. -/// -/// - Mixing multiple shared objects within the same transaction potentially leads -/// to all objects being slowed down depending on how loaded they are. -/// -/// - The only way to effectively use multiple shared objects is to be able to call -/// a function in Move which makes use of them. And these actions require `public` -/// function visibility. -/// -/// Case in this example is a mocked up liquidity pool implementation with two -/// functions: `swap` - available only directly because of the `entry` visibility -/// and a `dev_swap` - doing the same thing but can be used on chain (ie for -/// cross-chain swaps), and can be used in a function with other shared objects. -/// -/// The former is free because this functionality is meant to be available to -/// everyone. While the latter gives additional profits to the liquidity pool by -/// charging extensive (and potentially slowing) usage. -/// -module defi::dev_pass { - /// For when Subscription object no longer has uses. - const ENoUses: u64 = 0; - - /// Owned object from which SingleUses are spawn. - public struct Subscription has key { - id: UID, - uses: u64 - } - - /// A single use potato to authorize actions. - public struct SingleUse {} - - // ======== Default Functions ======== - - /// Public view for the `Subscription`.`uses` field. - public fun uses(s: &Subscription): u64 { s.uses } - - /// If `Subscription` is owned, create `SingleUse` (hot potato) to use in the service. - public fun use_pass(s: &mut Subscription): SingleUse { - assert!(s.uses != 0, ENoUses); - s.uses = s.uses - 1; - SingleUse {} - } - - /// Burn a subscription without checking for number of uses. Allows Sui storage refunds - /// when subscription is no longer needed. - entry public fun destroy(s: Subscription) { - let Subscription { id, uses: _ } = s; - id.delete(); - } - - // ======== Implementable Functions ======== - - /// Function to issue new `Subscription` with a specified number of uses. - /// Implementable by an external module with a witness parameter T. Number of - /// uses is determined by the actual implementation. - public fun issue_subscription(_w: T, uses: u64, ctx: &mut TxContext): Subscription { - Subscription { - id: object::new(ctx), - uses - } - } - - /// Increase number of uses in the subscription. - /// Implementable by an external module with a witness parameter T. - public fun add_uses(_w: T, s: &mut Subscription, uses: u64) { - s.uses = s.uses + uses; - } - - /// Confirm a use of a pass. Verified by the module that implements "Subscription API". - /// Implementable by an external module with a witness parameter T. Confirmation is only - /// available if the third party implements it and recognizes the use. - public fun confirm_use(_w: T, pass: SingleUse) { - let SingleUse { } = pass; - } - - /// Allow applications customize transferability of the `Subscription`. - /// Implementable by an external module with a witness parameter T. Module can define whether - /// a `Subscription` can be transferred to another account or not. Omitting this implementation - /// will mean that the `Subscription` can not be transferred. - public fun transfer(_w: T, s: Subscription, to: address) { - transfer::transfer(s, to) - } -} - -/// Rough outline of an AMM. -/// For simplicity pool implementation details are omitted but marked as comments to -/// show correlation with the `defi/pool.move` example. -module defi::some_amm { - use defi::dev_pass::{Self, Subscription, SingleUse}; - - /// A type to Mark subscription - public struct DEVPASS has drop {} - /* Can be customized to: DEVPASS to make one subscription per pool */ - /* And the price could be determined based on amount LP tokens issued or trade volume */ - - /// Entry function that uses a shared pool object. - /// Can only be accessed from outside of the chain - can't be called from another module. - entry fun swap(/* &mut Pool, Coin ... */) { /* ... */ } - - /// Function similar to the `swap` but can be called from other Move modules. - /// Opens up tooling; potentially slows down the AMM if multiple shared objects - /// used in the same tx! And for that developers have to pay. - public fun dev_swap(p: SingleUse, /* &mut Pool, Coin */): bool /* Coin */ { - dev_pass::confirm_use(DEVPASS {}, p); - /* ... */ - true - } - - /// Lastly there should a logic to purchase subscription. This AMM disallows transfers and - /// issues Subscription object to the sender address. - public fun purchase_pass(/* Coin , */ ctx: &mut TxContext) { - dev_pass::transfer( - DEVPASS {}, - dev_pass::issue_subscription(DEVPASS {}, 100, ctx), - ctx.sender() - ) - } - - /// Adds more uses to the `Subscription` object. - public fun topup_pass(s: &mut Subscription /* Coin */) { - dev_pass::add_uses(DEVPASS {}, s, 10) - } -} - -/// Sketch for an application that uses multiple pools. -/// Shows how subscriptions are used in `some_amm` module. -module defi::smart_swapper { - use defi::some_amm::{Self, DEVPASS}; - use defi::dev_pass::{Self, Subscription}; - - // just some types to use as generics for pools - public struct ETH {} public struct BTC {} public struct KTS {} - - /// Some function that allows cross-pool operations with some additional - /// logic. The most important part here is the use of subscription to "pay" - /// for each pool's access. - entry fun cross_pool_swap( - s: &mut Subscription - /* assuming a lot of arguments */ - ) { - - let _a = some_amm::dev_swap(dev_pass::use_pass(s) /*, Coin */); - let _b = some_amm::dev_swap(dev_pass::use_pass(s) /*, _a */); - - // do something with swapped values ? - // transfer::public_transfer( ... ) - } -} diff --git a/sui_programmability/examples/defi/tests/escrow_tests.move b/sui_programmability/examples/defi/tests/escrow_tests.move deleted file mode 100644 index b5d9873ec9a7a..0000000000000 --- a/sui_programmability/examples/defi/tests/escrow_tests.move +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module defi::escrow_tests { - use sui::test_scenario::{Self, Scenario}; - - use defi::escrow::{Self, EscrowedObj}; - - const ALICE_ADDRESS: address = @0xACE; - const BOB_ADDRESS: address = @0xACEB; - const THIRD_PARTY_ADDRESS: address = @0xFACE; - const RANDOM_ADDRESS: address = @123; - - // Error codes. - const ESwapTransferFailed: u64 = 0; - const EReturnTransferFailed: u64 = 0; - - // Example of an object type used for exchange - public struct ItemA has key, store { - id: UID - } - - // Example of the other object type used for exchange - public struct ItemB has key, store { - id: UID - } - - #[test] - fun test_escrow_flow() { - // Both Alice and Bob send items to the third party - let mut scenario_val = send_to_escrow(ALICE_ADDRESS, BOB_ADDRESS); - let scenario = &mut scenario_val; - - swap(scenario, THIRD_PARTY_ADDRESS); - - // Alice now owns item B, and Bob now owns item A - assert!(owns_object(ALICE_ADDRESS), ESwapTransferFailed); - assert!(owns_object(BOB_ADDRESS), ESwapTransferFailed); - - test_scenario::end(scenario_val); - } - - #[test] - fun test_return_to_sender() { - // Both Alice and Bob send items to the third party - let mut scenario_val = send_to_escrow(ALICE_ADDRESS, BOB_ADDRESS); - let scenario = &mut scenario_val; - - // The third party returns item A to Alice, item B to Bob - scenario.next_tx(THIRD_PARTY_ADDRESS); - { - let item_a = scenario.take_from_sender>(); - escrow::return_to_sender(item_a); - - let item_b = scenario.take_from_sender>(); - escrow::return_to_sender(item_b); - }; - scenario.next_tx(THIRD_PARTY_ADDRESS); - // Alice now owns item A, and Bob now owns item B - assert!(owns_object(ALICE_ADDRESS), EReturnTransferFailed); - assert!(owns_object(BOB_ADDRESS), EReturnTransferFailed); - - scenario_val.end(); - } - - #[test] - #[expected_failure(abort_code = escrow::EMismatchedExchangeObject)] - fun test_swap_wrong_objects() { - // Both Alice and Bob send items to the third party except that Alice wants to exchange - // for a different object than Bob's - let mut scenario = send_to_escrow_with_overrides(ALICE_ADDRESS, BOB_ADDRESS, true, false); - swap(&mut scenario, THIRD_PARTY_ADDRESS); - scenario.end(); - } - - #[test] - #[expected_failure(abort_code = escrow::EMismatchedSenderRecipient)] - fun test_swap_wrong_recipient() { - // Both Alice and Bob send items to the third party except that Alice put a different - // recipient than Bob - let mut scenario = send_to_escrow_with_overrides(ALICE_ADDRESS, BOB_ADDRESS, false, true); - swap(&mut scenario, THIRD_PARTY_ADDRESS); - scenario.end(); - } - - fun swap(scenario: &mut Scenario, third_party: address) { - scenario.next_tx(third_party); - { - let item_a = test_scenario::take_from_sender>(scenario); - let item_b = test_scenario::take_from_sender>(scenario); - escrow::swap(item_a, item_b); - }; - scenario.next_tx(third_party); - } - - fun send_to_escrow( - alice: address, - bob: address, - ): Scenario { - send_to_escrow_with_overrides(alice, bob, false, false) - } - - fun send_to_escrow_with_overrides( - alice: address, - bob: address, - override_exchange_for: bool, - override_recipient: bool, - ): Scenario { - let mut new_scenario = test_scenario::begin(alice); - let scenario = &mut new_scenario; - let ctx = scenario.ctx(); - let item_a_versioned_id = object::new(ctx); - - scenario.next_tx(bob); - let ctx = scenario.ctx(); - let item_b_versioned_id = object::new(ctx); - - let item_a_id = item_a_versioned_id.uid_to_inner(); - let mut item_b_id = item_b_versioned_id.uid_to_inner(); - if (override_exchange_for) { - item_b_id = object::id_from_address(RANDOM_ADDRESS); - }; - - // Alice sends item A to the third party - scenario.next_tx(alice); - { - let ctx = scenario.ctx(); - let escrowed = ItemA { - id: item_a_versioned_id - }; - let mut recipient = bob; - if (override_recipient) { - recipient = RANDOM_ADDRESS; - }; - escrow::create( - recipient, - THIRD_PARTY_ADDRESS, - item_b_id, - escrowed, - ctx - ); - }; - - // Bob sends item B to the third party - scenario.next_tx(BOB_ADDRESS); - { - let ctx = scenario.ctx(); - let escrowed = ItemB { - id: item_b_versioned_id - }; - escrow::create( - alice, - THIRD_PARTY_ADDRESS, - item_a_id, - escrowed, - ctx - ); - }; - scenario.next_tx(BOB_ADDRESS); - new_scenario - } - - fun owns_object(owner: address): bool { - test_scenario::has_most_recent_for_address(owner) - } -} diff --git a/sui_programmability/examples/defi/tests/flash_lender_tests.move b/sui_programmability/examples/defi/tests/flash_lender_tests.move deleted file mode 100644 index ab0e84f7842ce..0000000000000 --- a/sui_programmability/examples/defi/tests/flash_lender_tests.move +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module defi::flash_lender_tests { - use defi::flash_lender::{Self, AdminCap, FlashLender}; - use sui::pay; - use sui::coin; - use sui::sui::SUI; - use sui::test_scenario; - - #[test] - fun flash_loan_example() { - let admin = @0x1; - let borrower = @0x2; - - // admin creates a flash lender with 100 coins and a fee of 1 coin - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - { - let ctx = scenario.ctx(); - let coin = coin::mint_for_testing(100, ctx); - flash_lender::create(coin, 1, ctx); - }; - // borrower requests and repays a loan of 10 coins + the fee - test_scenario::next_tx(scenario, borrower); - { - let mut lender_val = scenario.take_shared>(); - let lender = &mut lender_val; - let ctx = scenario.ctx(); - - let (loan, receipt) = flash_lender::loan(lender, 10, ctx); - // in practice, borrower does something (e.g., arbitrage) to make a profit from the loan. - // simulate this by minting the borrower 5 coins. - let mut profit = coin::mint_for_testing(5, ctx); - coin::join(&mut profit, loan); - let to_keep = coin::take(profit.balance_mut(), 4, ctx); - pay::keep(to_keep, ctx); - flash_lender::repay(lender, profit, receipt); - - test_scenario::return_shared(lender_val); - }; - // admin withdraws the 1 coin profit from lending - scenario.next_tx( admin); - { - let mut lender_val = scenario.take_shared>(); - let lender = &mut lender_val; - let admin_cap = scenario.take_from_sender(); - let ctx = scenario.ctx(); - - // max loan size should have increased because of the fee payment - assert!(flash_lender::max_loan(lender) == 101, 0); - // withdraw 1 coin from the pool available for lending - let coin = flash_lender::withdraw(lender, &admin_cap, 1, ctx); - // max loan size should decrease accordingly - assert!(flash_lender::max_loan(lender) == 100, 0); - pay::keep(coin, ctx); - - test_scenario::return_shared(lender_val); - scenario.return_to_sender(admin_cap); - }; - scenario_val.end(); - } -} diff --git a/sui_programmability/examples/defi/tests/shared_escrow_test.move b/sui_programmability/examples/defi/tests/shared_escrow_test.move deleted file mode 100644 index 0c733a3d98e04..0000000000000 --- a/sui_programmability/examples/defi/tests/shared_escrow_test.move +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module defi::shared_escrow_tests { - use sui::test_scenario::{Self, Scenario}; - - use defi::shared_escrow::{Self, EscrowedObj}; - - const ALICE_ADDRESS: address = @0xACE; - const BOB_ADDRESS: address = @0xACEB; - const RANDOM_ADDRESS: address = @123; - - // Error codes. - const ESwapTransferFailed: u64 = 0; - const EReturnTransferFailed: u64 = 0; - - // Example of an object type used for exchange - public struct ItemA has key, store { - id: UID - } - - // Example of the other object type used for exchange - public struct ItemB has key, store { - id: UID - } - - #[test] - fun test_escrow_flow() { - // Alice creates the escrow - let (mut scenario_val, item_b) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - - // Bob exchanges item B for the escrowed item A - exchange(&mut scenario_val, BOB_ADDRESS, item_b); - - // Alice now owns item B, and Bob now owns item A - assert!(owns_object(ALICE_ADDRESS), ESwapTransferFailed); - assert!(owns_object(BOB_ADDRESS), ESwapTransferFailed); - - scenario_val.end(); - } - - #[test] - fun test_cancel() { - // Alice creates the escrow - let (mut scenario_val, ItemB { id }) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - id.delete(); - let scenario = &mut scenario_val; - // Alice does not own item A - assert!(!owns_object(ALICE_ADDRESS), EReturnTransferFailed); - - // Alice cancels the escrow - cancel(scenario, ALICE_ADDRESS); - - // Alice now owns item A - assert!(owns_object(ALICE_ADDRESS), EReturnTransferFailed); - - scenario_val.end(); - } - - #[test] - #[expected_failure(abort_code = shared_escrow::EWrongOwner)] - fun test_cancel_with_wrong_owner() { - // Alice creates the escrow - let (mut scenario_val, ItemB { id }) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - id.delete(); - let scenario = &mut scenario_val; - - // Bob tries to cancel the escrow that Alice owns and expects failure - cancel(scenario, BOB_ADDRESS); - - scenario_val.end(); - } - - #[test] - #[expected_failure(abort_code = shared_escrow::EWrongExchangeObject)] - fun test_swap_wrong_objects() { - // Alice creates the escrow in exchange for item b - let (mut scenario_val, ItemB { id }) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - id.delete(); - let scenario = &mut scenario_val; - - // Bob tries to exchange item C for the escrowed item A and expects failure - scenario.next_tx(BOB_ADDRESS); - let ctx = scenario.ctx(); - let item_c = ItemB { id: object::new(ctx) }; - exchange(scenario, BOB_ADDRESS, item_c); - - scenario_val.end(); - } - - #[test] - #[expected_failure(abort_code = shared_escrow::EWrongRecipient)] - fun test_swap_wrong_recipient() { - // Alice creates the escrow in exchange for item b - let (mut scenario_val, item_b) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - let scenario = &mut scenario_val; - - // Random address tries to exchange item B for the escrowed item A and expects failure - exchange(scenario, RANDOM_ADDRESS, item_b); - - scenario_val.end(); - } - - #[test] - #[expected_failure(abort_code = shared_escrow::EAlreadyExchangedOrCancelled)] - fun test_cancel_twice() { - // Alice creates the escrow - let (mut scenario_val, ItemB { id }) = create_escrow(ALICE_ADDRESS, BOB_ADDRESS); - id.delete(); - let scenario = &mut scenario_val; - // Alice does not own item A - assert!(!owns_object(ALICE_ADDRESS), EReturnTransferFailed); - - // Alice cancels the escrow - cancel(scenario, ALICE_ADDRESS); - - // Alice now owns item A - assert!(owns_object(ALICE_ADDRESS), EReturnTransferFailed); - - // Alice tries to cancel the escrow again - cancel(scenario, ALICE_ADDRESS); - - scenario_val.end(); - } - - fun cancel(scenario: &mut Scenario, initiator: address) { - scenario.next_tx(initiator); - { - let mut escrow_val = scenario.take_shared>(); - let escrow = &mut escrow_val; - let ctx = scenario.ctx(); - shared_escrow::cancel(escrow, ctx); - test_scenario::return_shared(escrow_val); - }; - scenario.next_tx(initiator); - } - - fun exchange(scenario: &mut Scenario, bob: address, item_b: ItemB) { - scenario.next_tx(bob); - { - let mut escrow_val = scenario.take_shared>(); - let escrow = &mut escrow_val; - let ctx = scenario.ctx(); - shared_escrow::exchange(item_b, escrow, ctx); - test_scenario::return_shared(escrow_val); - }; - scenario.next_tx(bob); - } - - fun create_escrow( - alice: address, - bob: address, - ): (Scenario, ItemB) { - let mut new_scenario = test_scenario::begin(alice); - let scenario = &mut new_scenario; - let ctx = scenario.ctx(); - let item_a_versioned_id = object::new(ctx); - - scenario.next_tx(bob); - let ctx = scenario.ctx(); - let item_b = ItemB { id: object::new(ctx) }; - let item_b_id = object::id(&item_b); - - // Alice creates the escrow - scenario.next_tx(alice); - { - let ctx = scenario.ctx(); - let escrowed = ItemA { - id: item_a_versioned_id - }; - shared_escrow::create( - bob, - item_b_id, - escrowed, - ctx - ); - }; - scenario.next_tx(alice); - (new_scenario, item_b) - } - - fun owns_object(owner: address): bool { - test_scenario::has_most_recent_for_address(owner) - } -} diff --git a/sui_programmability/examples/ecommerce/Move.toml b/sui_programmability/examples/ecommerce/Move.toml deleted file mode 100644 index 5d5ecc5566b7d..0000000000000 --- a/sui_programmability/examples/ecommerce/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "ecommerce" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -ecommerce = "0x0" diff --git a/sui_programmability/examples/ecommerce/README.md b/sui_programmability/examples/ecommerce/README.md deleted file mode 100644 index 78db0f21b82e8..0000000000000 --- a/sui_programmability/examples/ecommerce/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# E-Commerce Platform - -This application aims to implement a simple generic ecommerce platform with the following set of features: - -1. Allow anyone create their shop with a set of listings; -2. Shop should support listing customization + selling both digital (simple flow) and "physical" items; -3. Create a list-purchase-deliver flow with the use of Sui Objects; -4. Support platform economy by taking a fee from every purchase; -5. Implement a simple bonus points acquirable on purchase which then can be used to get a discount or special offer; diff --git a/sui_programmability/examples/ecommerce/sources/.gitkeep b/sui_programmability/examples/ecommerce/sources/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/sui_programmability/examples/ecommerce/tests/.gitkeep b/sui_programmability/examples/ecommerce/tests/.gitkeep deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/sui_programmability/examples/fungible_tokens/Move.toml b/sui_programmability/examples/fungible_tokens/Move.toml deleted file mode 100644 index 51dc93ddd9d7e..0000000000000 --- a/sui_programmability/examples/fungible_tokens/Move.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "FungibleTokens" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -fungible_tokens = "0x0" -abc = "0x0" -rc = "0x0" diff --git a/sui_programmability/examples/fungible_tokens/README.md b/sui_programmability/examples/fungible_tokens/README.md deleted file mode 100644 index 5ae0f2358c79b..0000000000000 --- a/sui_programmability/examples/fungible_tokens/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Fungible Tokens - -* MANAGED: a token managed by a treasurer trusted for minting and burning. This is how (e.g.) a fiat-backed stablecoin or an in-game virtual currency would work. -* BASKET: a synthetic token backed by a basket of other assets. This how (e.g.) a [Special Drawing Rights (SDR)](https://www.imf.org/en/About/Factsheets/Sheets/2016/08/01/14/51/Special-Drawing-Right-SDR)-like asset would work. -* REGULATED_COIN: a coin managed by a central authority which can freeze accounts -* PRIVATE_COIN: a coin which has the option of hiding the amount that you transact -* PRIVATE_BALANCE: a balance which has the option of hiding the amount that it stores. -* FIXED: a token with a fixed supply (coming in future). -* ALGO: a token with an algorithmic issuance policy (coming in future). diff --git a/sui_programmability/examples/fungible_tokens/sources/basket.move b/sui_programmability/examples/fungible_tokens/sources/basket.move deleted file mode 100644 index 91cbdeb29e200..0000000000000 --- a/sui_programmability/examples/fungible_tokens/sources/basket.move +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// A synthetic fungible token backed by a basket of other tokens. -/// Here, we use a basket that is 1:1 SUI and MANAGED, -/// but this approach would work for a basket with arbitrary assets/ratios. -/// E.g., [SDR](https://www.imf.org/en/About/Factsheets/Sheets/2016/08/01/14/51/Special-Drawing-Right-SDR) -/// could be implemented this way. -module fungible_tokens::basket { - use fungible_tokens::managed::MANAGED; - use sui::coin::{Self, Coin}; - use sui::balance::{Self, Balance, Supply}; - use sui::sui::SUI; - - /// Name of the coin. By convention, this type has the same name as its parent module - /// and has no fields. The full type of the coin defined by this module will be `COIN`. - public struct BASKET has drop { } - - /// Singleton shared object holding the reserve assets and the capability. - public struct Reserve has key { - id: UID, - /// capability allowing the reserve to mint and burn BASKET - total_supply: Supply, - /// SUI coins held in the reserve - sui: Balance, - /// MANAGED coins held in the reserve - managed: Balance, - } - - /// Needed to deposit a 1:1 ratio of SUI and MANAGED for minting, but deposited a different ratio - const EBadDepositRatio: u64 = 0; - - #[allow(unused_function)] - fun init(witness: BASKET, ctx: &mut TxContext) { - // Get a treasury cap for the coin put it in the reserve - let total_supply = balance::create_supply(witness); - - transfer::share_object(Reserve { - id: object::new(ctx), - total_supply, - sui: balance::zero(), - managed: balance::zero(), - }) - } - - /// === Writes === - - /// Mint BASKET coins by accepting an equal number of SUI and MANAGED coins - public fun mint( - reserve: &mut Reserve, sui: Coin, managed: Coin, ctx: &mut TxContext - ): Coin { - let num_sui = coin::value(&sui); - assert!(num_sui == coin::value(&managed), EBadDepositRatio); - - coin::put(&mut reserve.sui, sui); - coin::put(&mut reserve.managed, managed); - - let minted_balance = balance::increase_supply(&mut reserve.total_supply, num_sui); - - coin::from_balance(minted_balance, ctx) - } - - /// Burn BASKET coins and return the underlying reserve assets - public fun burn( - reserve: &mut Reserve, basket: Coin, ctx: &mut TxContext - ): (Coin, Coin) { - let num_basket = balance::decrease_supply(&mut reserve.total_supply, coin::into_balance(basket)); - let sui = coin::take(&mut reserve.sui, num_basket, ctx); - let managed = coin::take(&mut reserve.managed, num_basket, ctx); - - (sui, managed) - } - - // === Reads === - - /// Return the number of `MANAGED` coins in circulation - public fun total_supply(reserve: &Reserve): u64 { - balance::supply_value(&reserve.total_supply) - } - - /// Return the number of SUI in the reserve - public fun sui_supply(reserve: &Reserve): u64 { - balance::value(&reserve.sui) - } - - /// Return the number of MANAGED in the reserve - public fun managed_supply(reserve: &Reserve): u64 { - balance::value(&reserve.managed) - } - - #[test_only] - public fun init_for_testing(ctx: &mut TxContext) { - init(BASKET {}, ctx) - } -} diff --git a/sui_programmability/examples/fungible_tokens/sources/managed.move b/sui_programmability/examples/fungible_tokens/sources/managed.move deleted file mode 100644 index e41ca15766f5a..0000000000000 --- a/sui_programmability/examples/fungible_tokens/sources/managed.move +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Example coin with a trusted manager responsible for minting/burning (e.g., a stablecoin) -/// By convention, modules defining custom coin types use upper case names, in contrast to -/// ordinary modules, which use camel case. -module fungible_tokens::managed { - use sui::coin::{Self, Coin, TreasuryCap}; - - /// Name of the coin. By convention, this type has the same name as its parent module - /// and has no fields. The full type of the coin defined by this module will be `COIN`. - public struct MANAGED has drop {} - - #[allow(unused_function)] - /// Register the managed currency to acquire its `TreasuryCap`. Because - /// this is a module initializer, it ensures the currency only gets - /// registered once. - fun init(witness: MANAGED, ctx: &mut TxContext) { - // Get a treasury cap for the coin and give it to the transaction sender - let (treasury_cap, metadata) = coin::create_currency(witness, 2, b"MANAGED", b"", b"", option::none(), ctx); - transfer::public_freeze_object(metadata); - transfer::public_transfer(treasury_cap, tx_context::sender(ctx)) - } - - /// Manager can mint new coins - public entry fun mint( - treasury_cap: &mut TreasuryCap, amount: u64, recipient: address, ctx: &mut TxContext - ) { - coin::mint_and_transfer(treasury_cap, amount, recipient, ctx) - } - - /// Manager can burn coins - public entry fun burn(treasury_cap: &mut TreasuryCap, coin: Coin) { - coin::burn(treasury_cap, coin); - } - - #[test_only] - /// Wrapper of module initializer for testing - public fun test_init(ctx: &mut TxContext) { - init(MANAGED {}, ctx) - } -} diff --git a/sui_programmability/examples/fungible_tokens/sources/regulated_coin.move b/sui_programmability/examples/fungible_tokens/sources/regulated_coin.move deleted file mode 100644 index 657943fe86291..0000000000000 --- a/sui_programmability/examples/fungible_tokens/sources/regulated_coin.move +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Module representing a common type for regulated coins. Features balance -/// accessors which can be used to implement a RegulatedCoin interface. -/// -/// To implement any of the methods, module defining the type for the currency -/// is expected to implement the main set of methods such as `borrow()`, -/// `borrow_mut()` and `zero()`. -/// -/// Each of the methods of this module requires a Witness struct to be sent. -module rc::regulated_coin { - use sui::balance::{Self, Balance}; - - /// The RegulatedCoin struct; holds a common `Balance` which is compatible - /// with all the other Coins and methods, as well as the `creator` field, which - /// can be used for additional security/regulation implementations. - public struct RegulatedCoin has key, store { - id: UID, - balance: Balance, - creator: address - } - - /// Get the `RegulatedCoin.balance.value` field; - public fun value(c: &RegulatedCoin): u64 { - balance::value(&c.balance) - } - - /// Get the `RegulatedCoin.creator` field; - public fun creator(c: &RegulatedCoin): address { - c.creator - } - - // === Necessary set of Methods (provide security guarantees and balance access) === - - /// Get an immutable reference to the Balance of a RegulatedCoin; - public fun borrow(_: T, coin: &RegulatedCoin): &Balance { - &coin.balance - } - - /// Get a mutable reference to the Balance of a RegulatedCoin; - public fun borrow_mut(_: T, coin: &mut RegulatedCoin): &mut Balance { - &mut coin.balance - } - - /// Author of the currency can restrict who is allowed to create new balances; - public fun zero(_: T, creator: address, ctx: &mut TxContext): RegulatedCoin { - RegulatedCoin { id: object::new(ctx), balance: balance::zero(), creator } - } - - /// Build a transferable `RegulatedCoin` from a `Balance`; - public fun from_balance( - _: T, balance: Balance, creator: address, ctx: &mut TxContext - ): RegulatedCoin { - RegulatedCoin { id: object::new(ctx), balance, creator } - } - - /// Destroy `RegulatedCoin` and return its `Balance`; - public fun into_balance(_: T, coin: RegulatedCoin): Balance { - let RegulatedCoin { balance, creator: _, id } = coin; - sui::object::delete(id); - balance - } - - // === Optional Methods (can be used for simpler implementation of basic operations) === - - /// Join Balances of a `RegulatedCoin` c1 and `RegulatedCoin` c2. - public fun join(witness: T, c1: &mut RegulatedCoin, c2: RegulatedCoin) { - balance::join(&mut c1.balance, into_balance(witness, c2)); - } - - /// Subtract `RegulatedCoin` with `value` from `RegulatedCoin`. - /// - /// This method does not provide any checks by default and can possibly lead to mocking - /// behavior of `Regulatedcoin::zero()` when a value is 0. So in case empty balances - /// should not be allowed, this method should be additionally protected against zero value. - public fun split( - witness: T, c1: &mut RegulatedCoin, creator: address, value: u64, ctx: &mut TxContext - ): RegulatedCoin { - let balance = balance::split(&mut c1.balance, value); - from_balance(witness, balance, creator, ctx) - } -} - -/// Abc is a RegulatedCoin which: -/// -/// - is managed account creation (only admins can create a new balance) -/// - has a denylist for addresses managed by the coin admins -/// - has restricted transfers which can not be taken by anyone except the recipient -module abc::abc { - use rc::regulated_coin::{Self as rcoin, RegulatedCoin as RCoin}; - use sui::balance::{Self, Supply, Balance}; - use sui::coin::{Self, Coin}; - - /// The ticker of Abc regulated token - public struct Abc has drop {} - - /// A restricted transfer of Abc to another account. - public struct Transfer has key { - id: UID, - balance: Balance, - to: address, - } - - /// A registry of addresses banned from using the coin. - public struct Registry has key { - id: UID, - banned: vector
, - swapped_amount: u64, - } - - /// A AbcTreasuryCap for the balance::Supply. - public struct AbcTreasuryCap has key, store { - id: UID, - supply: Supply - } - - /// For when an attempting to interact with another account's RegulatedCoin. - const ENotOwner: u64 = 1; - - /// For when address has been banned and someone is trying to access the balance - const EAddressBanned: u64 = 2; - - #[allow(unused_function)] - /// Create the Abc currency and send the AbcTreasuryCap to the creator - /// as well as the first (and empty) balance of the RegulatedCoin. - /// - /// Also creates a shared Registry which holds banned addresses. - fun init(ctx: &mut TxContext) { - let sender = tx_context::sender(ctx); - let treasury_cap = AbcTreasuryCap { - id: object::new(ctx), - supply: balance::create_supply(Abc {}) - }; - - transfer::public_transfer(zero(sender, ctx), sender); - transfer::public_transfer(treasury_cap, sender); - - transfer::share_object(Registry { - id: object::new(ctx), - banned: vector::empty(), - swapped_amount: 0, - }); - } - - // === Getters section: Registry === - - /// Get total amount of `Coin` from the `Registry`. - public fun swapped_amount(r: &Registry): u64 { - r.swapped_amount - } - - /// Get vector of banned addresses from `Registry`. - public fun banned(r: &Registry): &vector
{ - &r.banned - } - - // === Admin actions: creating balances, minting coins and banning addresses === - - /// Create an empty `RCoin` instance for account `for`. AbcTreasuryCap is passed for - /// authentication purposes - only admin can create new accounts. - public entry fun create(_: &AbcTreasuryCap, `for`: address, ctx: &mut TxContext) { - transfer::public_transfer(zero(`for`, ctx), `for`) - } - - /// Mint more Abc. Requires AbcTreasuryCap for authorization, so can only be done by admins. - public entry fun mint(treasury: &mut AbcTreasuryCap, owned: &mut RCoin, value: u64) { - balance::join(borrow_mut(owned), balance::increase_supply(&mut treasury.supply, value)); - } - - /// Burn `value` amount of `RCoin`. Requires AbcTreasuryCap for authorization, so can only be done by admins. - /// - /// TODO: Make AbcTreasuryCap a part of Balance module instead of Coin. - public entry fun burn(treasury: &mut AbcTreasuryCap, owned: &mut RCoin, value: u64) { - balance::decrease_supply( - &mut treasury.supply, - balance::split(borrow_mut(owned), value) - ); - } - - /// Ban some address and forbid making any transactions from or to this address. - /// Only owner of the AbcTreasuryCap can perform this action. - public entry fun ban(_cap: &AbcTreasuryCap, registry: &mut Registry, to_ban: address) { - vector::push_back(&mut registry.banned, to_ban) - } - - // === Public: Regulated transfers === - - /// Transfer entrypoint - create a restricted `Transfer` instance and transfer it to the - /// `to` account for being accepted later. - /// Fails if sender is not an creator of the `RegulatedCoin` or if any of the parties is in - /// the ban list in Registry. - public entry fun transfer(r: &Registry, coin: &mut RCoin, value: u64, to: address, ctx: &mut TxContext) { - let sender = tx_context::sender(ctx); - - assert!(rcoin::creator(coin) == sender, ENotOwner); - assert!(vector::contains(&r.banned, &to) == false, EAddressBanned); - assert!(vector::contains(&r.banned, &sender) == false, EAddressBanned); - - transfer::transfer(Transfer { - to, - id: object::new(ctx), - balance: balance::split(borrow_mut(coin), value), - }, to) - } - - /// Accept an incoming transfer by joining an incoming balance with an owned one. - /// - /// Fails if: - /// 1. the `RegulatedCoin.creator` does not match `Transfer.to`; - /// 2. the address of the creator/recipient is banned; - public entry fun accept_transfer(r: &Registry, coin: &mut RCoin, transfer: Transfer) { - let Transfer { id, balance, to } = transfer; - - assert!(rcoin::creator(coin) == to, ENotOwner); - assert!(vector::contains(&r.banned, &to) == false, EAddressBanned); - - balance::join(borrow_mut(coin), balance); - object::delete(id) - } - - // === Public: Swap RegulatedCoin <-> Coin === - - /// Take `value` amount of `RegulatedCoin` and make it freely transferable by wrapping it into - /// a `Coin`. Update `Registry` to keep track of the swapped amount. - /// - /// Fails if: - /// 1. `RegulatedCoin.creator` was banned; - /// 2. `RegulatedCoin` is not owned by the tx sender; - public entry fun take(r: &mut Registry, coin: &mut RCoin, value: u64, ctx: &mut TxContext) { - let sender = tx_context::sender(ctx); - - assert!(rcoin::creator(coin) == sender, ENotOwner); - assert!(vector::contains(&r.banned, &sender) == false, EAddressBanned); - - // Update swapped amount for Registry to keep track of non-regulated amounts. - r.swapped_amount = r.swapped_amount + value; - - transfer::public_transfer(coin::take(borrow_mut(coin), value, ctx), sender); - } - - /// Take `Coin` and put to the `RegulatedCoin`'s balance. - /// - /// Fails if: - /// 1. `RegulatedCoin.creator` was banned; - /// 2. `RegulatedCoin` is not owned by the tx sender; - public entry fun put_back(r: &mut Registry, rc_coin: &mut RCoin, coin: Coin, ctx: &TxContext) { - let balance = coin::into_balance(coin); - let sender = tx_context::sender(ctx); - - assert!(rcoin::creator(rc_coin) == sender, ENotOwner); - assert!(vector::contains(&r.banned, &sender) == false, EAddressBanned); - - // Update swapped amount as in `swap_regulated`. - r.swapped_amount = r.swapped_amount - balance::value(&balance); - - balance::join(borrow_mut(rc_coin), balance); - } - - // === Private implementations accessors and type morphing === - - #[allow(unused_function)] - fun borrow(coin: &RCoin): &Balance { rcoin::borrow(Abc {}, coin) } - fun borrow_mut(coin: &mut RCoin): &mut Balance { rcoin::borrow_mut(Abc {}, coin) } - fun zero(creator: address, ctx: &mut TxContext): RCoin { rcoin::zero(Abc {}, creator, ctx) } - - // === Testing utilities === - - #[test_only] public fun init_for_testing(ctx: &mut TxContext) { init(ctx) } - #[test_only] public fun borrow_for_testing(coin: &RCoin): &Balance { borrow(coin) } - #[test_only] public fun borrow_mut_for_testing(coin: &mut RCoin): &Balance { borrow_mut(coin) } -} - -#[test_only] -/// Tests for the abc module. They are sequential and based on top of each other. -/// ``` -/// * - test_minting -/// | +-- test_creation -/// | +-- test_transfer -/// | +-- test_burn -/// | +-- test_take -/// | +-- test_put_back -/// | +-- test_ban -/// | +-- test_address_banned_fail -/// | +-- test_different_account_fail -/// | +-- test_not_owned_balance_fail -/// ``` -module abc::tests { - use abc::abc::{Self, Abc, AbcTreasuryCap, Registry}; - use rc::regulated_coin::{Self as rcoin, RegulatedCoin as RCoin}; - - use sui::test_scenario::{Self, Scenario, next_tx, ctx}; - - // === Test handlers; this trick helps reusing scenarios == - - #[test] - #[expected_failure(abort_code = ::abc::abc::EAddressBanned)] - fun test_address_banned_fail() { - let mut scenario = scenario(); - test_address_banned_fail_(&mut scenario); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = ::abc::abc::EAddressBanned)] - fun test_different_account_fail() { - let mut scenario = scenario(); - test_different_account_fail_(&mut scenario); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = ::abc::abc::ENotOwner)] - fun test_not_owned_balance_fail() { - let mut scenario = scenario(); - test_not_owned_balance_fail_(&mut scenario); - test_scenario::end(scenario); - } - - // === Helpers and basic test organization === - - fun scenario(): Scenario { test_scenario::begin(@0xAbc) } - fun people(): (address, address, address) { (@0xAbc, @0xE05, @0xFACE) } - - // Admin creates a regulated coin Abc and mints 1,000,000 of it. - fun test_minting_(test: &mut Scenario) { - let (admin, _, _) = people(); - - next_tx(test, admin); - { - abc::init_for_testing(ctx(test)) - }; - - next_tx(test, admin); - { - let mut cap = test_scenario::take_from_sender(test); - let mut coin = test_scenario::take_from_sender>(test); - - abc::mint(&mut cap, &mut coin, 1000000); - - assert!(rcoin::value(&coin) == 1000000, 0); - - test_scenario::return_to_sender(test, cap); - test_scenario::return_to_sender(test, coin); - } - } - - // Admin creates an empty balance for the `user1`. - fun test_creation_(test: &mut Scenario) { - let (admin, user1, _) = people(); - - test_minting_(test); - - next_tx(test, admin); - { - let cap = test_scenario::take_from_sender(test); - - abc::create(&cap, user1, ctx(test)); - - test_scenario::return_to_sender(test, cap); - }; - - next_tx(test, user1); - { - let coin = test_scenario::take_from_sender>(test); - - assert!(rcoin::creator(&coin) == user1, 1); - assert!(rcoin::value(&coin) == 0, 2); - - test_scenario::return_to_sender(test, coin); - }; - } - - // Admin transfers 500,000 coins to `user1`. - // User1 accepts the transfer and checks his balance. - fun test_transfer_(test: &mut Scenario) { - let (admin, user1, _) = people(); - - test_creation_(test); - - next_tx(test, admin); - { - let mut coin = test_scenario::take_from_sender>(test); - let reg = test_scenario::take_shared(test); - let reg_ref = ® - - abc::transfer(reg_ref, &mut coin, 500000, user1, ctx(test)); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, coin); - }; - - next_tx(test, user1); - { - let mut coin = test_scenario::take_from_sender>(test); - let transfer = test_scenario::take_from_sender(test); - let reg = test_scenario::take_shared(test); - let reg_ref = ® - - abc::accept_transfer(reg_ref, &mut coin, transfer); - - assert!(rcoin::value(&coin) == 500000, 3); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, coin); - }; - } - - // Admin bans user1 by adding his address to the registry. - fun test_ban_(test: &mut Scenario) { - let (admin, user1, _) = people(); - - test_transfer_(test); - - next_tx(test, admin); - { - let cap = test_scenario::take_from_sender(test); - let mut reg = test_scenario::take_shared(test); - let reg_ref = &mut reg; - - abc::ban(&cap, reg_ref, user1); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, cap); - }; - } - - // Banned User1 fails to create a Transfer. - fun test_address_banned_fail_(test: &mut Scenario) { - let (_, user1, user2) = people(); - - test_ban_(test); - - next_tx(test, user1); - { - let mut coin = test_scenario::take_from_sender>(test); - let reg = test_scenario::take_shared(test); - let reg_ref = ® - - abc::transfer(reg_ref, &mut coin, 250000, user2, ctx(test)); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, coin); - }; - } - - // User1 is banned. Admin tries to make a Transfer to User1 and fails - user banned. - fun test_different_account_fail_(test: &mut Scenario) { - let (admin, user1, _) = people(); - - test_ban_(test); - - next_tx(test, admin); - { - let mut coin = test_scenario::take_from_sender>(test); - let reg = test_scenario::take_shared(test); - let reg_ref = ® - - abc::transfer(reg_ref, &mut coin, 250000, user1, ctx(test)); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, coin); - }; - } - - // User1 is banned and transfers the whole balance to User2. - // User2 tries to use this balance and fails. - fun test_not_owned_balance_fail_(test: &mut Scenario) { - let (_, user1, user2) = people(); - - test_ban_(test); - - next_tx(test, user1); - { - let coin = test_scenario::take_from_sender>(test); - sui::transfer::public_transfer(coin, user2); - }; - - next_tx(test, user2); - { - let mut coin = test_scenario::take_from_sender>(test); - let reg = test_scenario::take_shared(test); - let reg_ref = ® - - abc::transfer(reg_ref, &mut coin, 500000, user1, ctx(test)); - - test_scenario::return_shared(reg); - test_scenario::return_to_sender(test, coin); - } - } -} diff --git a/sui_programmability/examples/fungible_tokens/sources/treasury_lock.move b/sui_programmability/examples/fungible_tokens/sources/treasury_lock.move deleted file mode 100644 index f51264d9af2aa..0000000000000 --- a/sui_programmability/examples/fungible_tokens/sources/treasury_lock.move +++ /dev/null @@ -1,441 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// WARNING: Like all files in the examples section, this code is unaudited -/// and should NOT be running in production. Using the code unaudited could potentially -/// result in lost of funds from hacks, and leakage of transaction amounts. - -/// An example implementation of a 'treasury lock'. It encapsulates the TreasuryCap -/// of a Coin so that additional whitelisted parties (bearers of the `MintCap`) -/// can mint new Coins up to a pre-defined per epoch limit. This can be used e.g. -/// to create a faucet. -module fungible_tokens::treasury_lock { - use sui::coin::{Self, TreasuryCap}; - use sui::balance::{Balance}; - use sui::vec_set::{Self, VecSet}; - - /// This mint capability instance is banned. - const EMintCapBanned: u64 = 0; - /// Requested mint amount exceeds the per epoch mint limit. - const EMintAmountTooLarge: u64 = 1; - - /// Encapsulates the `TreasuryCap` and stores the list of banned mint authorities. - public struct TreasuryLock has key { - id: UID, - treasury_cap: TreasuryCap, - banned_mint_authorities: VecSet - } - - /// Admin capability for `TreasuryLock`. Bearer has the power to create, ban, - /// and unban mint capabilities (`MintCap`) - public struct LockAdminCap has key, store { - id: UID - } - - /// Capability allowing the bearer to mint new Coins up to a pre-defined per epoch limit. - public struct MintCap has key, store { - id: UID, - max_mint_per_epoch: u64, - last_epoch: u64, - minted_in_epoch: u64 - } - - /// Create a new `TreasuryLock` for `TreasuryCap`. - public fun new_lock( - cap: TreasuryCap, ctx: &mut TxContext - ): LockAdminCap { - let lock = TreasuryLock { - id: object::new(ctx), - treasury_cap: cap, - banned_mint_authorities: vec_set::empty() - }; - transfer::share_object(lock); - - LockAdminCap { - id: object::new(ctx), - } - } - - /// Entry function. Creates a new `TreasuryLock` for `TreasuryCap`. Invokes `new_lock`. - public entry fun new_lock_(cap: TreasuryCap, ctx: &mut TxContext) { - transfer::public_transfer( - new_lock(cap, ctx), - tx_context::sender(ctx) - ) - } - - /// Create a new mint capability whose bearer will be allowed to mint - /// `max_mint_per_epoch` coins per epoch. - public fun create_mint_cap( - _cap: &LockAdminCap, max_mint_per_epoch: u64, ctx: &mut TxContext - ): MintCap { - MintCap{ - id: object::new(ctx), - max_mint_per_epoch, - last_epoch: tx_context::epoch(ctx), - minted_in_epoch: 0 - } - } - - /// Entry function. Creates a new mint capability whose bearer will be allowed - /// to mint `max_mint_per_epoch` coins per epoch. Sends it to `recipient`. - public fun create_and_transfer_mint_cap( - cap: &LockAdminCap, max_mint_per_epoch: u64, recipient: address, ctx: &mut TxContext - ) { - transfer::public_transfer( - create_mint_cap(cap, max_mint_per_epoch, ctx), - recipient - ) - } - - /// Ban a `MintCap`. - public fun ban_mint_cap_id( - _cap: &LockAdminCap, lock: &mut TreasuryLock, id: ID - ) { - vec_set::insert(&mut lock.banned_mint_authorities, id) - } - - /// Entry function. Bans a `MintCap`. - public entry fun ban_mint_cap_id_( - cap: &LockAdminCap, lock: &mut TreasuryLock, id: ID - ) { - ban_mint_cap_id(cap, lock, id); - } - - /// Unban a previously banned `MintCap`. - public fun unban_mint_cap_id( - _cap: &LockAdminCap, lock: &mut TreasuryLock, id: ID - ) { - vec_set::remove(&mut lock.banned_mint_authorities, &id) - } - - /// Entry function. Unbans a previously banned `MintCap`. - public entry fun unban_mint_cap_id_( - cap: &LockAdminCap, lock: &mut TreasuryLock, id: ID - ) { - unban_mint_cap_id(cap, lock, id); - } - - /// Borrow the `TreasuryCap` to use directly. - public fun treasury_cap_mut( - _cap: &LockAdminCap, lock: &mut TreasuryLock - ): &mut TreasuryCap { - &mut lock.treasury_cap - } - - /// Mint a `Balance` from a `TreasuryLock` providing a `MintCap`. - public fun mint_balance( - lock: &mut TreasuryLock, cap: &mut MintCap, amount: u64, ctx: &mut TxContext - ): Balance { - assert!( - !vec_set::contains(&lock.banned_mint_authorities, object::uid_as_inner(&cap.id)), - EMintCapBanned - ); - - let epoch = tx_context::epoch(ctx); - if (cap.last_epoch != epoch) { - cap.last_epoch = epoch; - cap.minted_in_epoch = 0; - }; - assert!( - cap.minted_in_epoch + amount <= cap.max_mint_per_epoch, - EMintAmountTooLarge - ); - - cap.minted_in_epoch = cap.minted_in_epoch + amount; - coin::mint_balance(&mut lock.treasury_cap, amount) - } - - /// Entry function. Mint a `Coin` from a `TreasuryLock` providing a `MintCap` - /// and transfer it to recipient. - public entry fun mint_and_transfer( - lock: &mut TreasuryLock, - cap: &mut MintCap, - amount: u64, - recipient: address, - ctx: &mut TxContext - ) { - let balance = mint_balance(lock, cap, amount, ctx); - transfer::public_transfer( - coin::from_balance(balance, ctx), - recipient - ) - } -} - -#[test_only] -module fungible_tokens::treasury_lock_tests { - use sui::test_scenario::{Self, Scenario}; - use sui::balance::{Self, Balance}; - use sui::coin; - use sui::test_utils; - use fungible_tokens::treasury_lock::{Self, TreasuryLock, LockAdminCap, MintCap, create_and_transfer_mint_cap, new_lock, mint_balance}; - - const ADMIN: address = @0xABBA; - const USER: address = @0xB0B; - - // one time witness for the coin used in tests - public struct TREASURY_LOCK_TESTS has drop {} - - fun user_with_mint_cap_scenario(): Scenario { - let mut scenario_ = test_scenario::begin(ADMIN); - let scenario = &mut scenario_; - - // create a currency and lock it - test_scenario::next_tx(scenario, ADMIN); - { - let treasury_lock_tests = test_utils::create_one_time_witness(); - let (treasury, metadata) = coin::create_currency(treasury_lock_tests, 0, b"", b"", b"", option::none(), test_scenario::ctx(scenario)); - transfer::public_freeze_object(metadata); - let admin_cap = new_lock(treasury, test_scenario::ctx(scenario)); - transfer::public_transfer( - admin_cap, - ADMIN - ) - }; - - // create a mint capability and transfer it to user - test_scenario::next_tx(scenario, ADMIN); - { - let admin_cap = test_scenario::take_from_sender>(scenario); - create_and_transfer_mint_cap(&admin_cap, 500, USER, test_scenario::ctx(scenario)); - test_scenario::return_to_sender(scenario, admin_cap); - }; - test_scenario::next_tx(scenario, ADMIN); - - return scenario_ - } - - fun user_mint_balance(scenario: &mut Scenario, amount: u64): Balance { - let mut mint_cap = test_scenario::take_from_sender>(scenario); - let mut lock = test_scenario::take_shared>(scenario); - - let balance = mint_balance( - &mut lock, - &mut mint_cap, - amount, - test_scenario::ctx(scenario) - ); - - test_scenario::return_to_sender(scenario, mint_cap); - test_scenario::return_shared(lock); - - balance - } - - - #[test] - fun test_user_can_mint() { - let mut scenario_ = user_with_mint_cap_scenario(); - let scenario = &mut scenario_; - - // user uses its capability to mint 300 coins - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 300); - assert!(balance::value(&balance) == 300, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - test_scenario::end(scenario_); - } - - #[test] - #[expected_failure(abort_code = treasury_lock::EMintAmountTooLarge)] - fun test_minting_over_limit_fails() { - let mut scenario_ = user_with_mint_cap_scenario(); - let scenario = &mut scenario_; - - // mint 300 coins - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 300); - assert!(balance::value(&balance) == 300, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - - // mint 200 more - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 200); - assert!(balance::value(&balance) == 200, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - - // attempt to mint amount over the epoch limit - should fail - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 1); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - test_scenario::end(scenario_); - } - - #[test] - fun test_minted_amount_resets_at_epoch_change() { - let mut scenario_ = user_with_mint_cap_scenario(); - let scenario = &mut scenario_; - - // mint 300 coins - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 300); - assert!(balance::value(&balance) == 300, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - - // next epoch and mint 300 again - test_scenario::next_epoch(scenario, USER); - { - let balance = user_mint_balance(scenario, 300); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - test_scenario::end(scenario_); - } - - #[test] - #[expected_failure(abort_code = treasury_lock::EMintCapBanned)] - fun test_banned_cap_cannot_mint() { - let mut scenario_ = user_with_mint_cap_scenario(); - let scenario = &mut scenario_; - - // get the mint cap ID for reference - test_scenario::next_tx(scenario, USER); - let mint_cap = test_scenario::take_from_sender>(scenario); - let mint_cap_id = object::id(&mint_cap); - test_scenario::return_to_sender(scenario, mint_cap); - - - // mint 100 coins - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 100); - assert!(balance::value(&balance) == 100, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - - // admin bans mint cap - test_scenario::next_tx(scenario, ADMIN); - { - let admin_cap = test_scenario::take_from_sender>(scenario); - let mut lock = test_scenario::take_shared>(scenario); - - treasury_lock::ban_mint_cap_id( - &admin_cap, - &mut lock, - mint_cap_id - ); - - test_scenario::return_to_sender(scenario, admin_cap); - test_scenario::return_shared(lock); - }; - - // user attempts to mint but fails - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 100); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - test_scenario::end(scenario_); - } - - #[test] - fun test_user_can_mint_after_unban() { - let mut scenario_ = user_with_mint_cap_scenario(); - let scenario = &mut scenario_; - - // get the mint cap ID for reference - test_scenario::next_tx(scenario, USER); - let mint_cap = test_scenario::take_from_sender>(scenario); - let mint_cap_id = object::id(&mint_cap); - test_scenario::return_to_sender(scenario, mint_cap); - - // mint 100 coins - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 100); - assert!(balance::value(&balance) == 100, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - - // admin bans mint cap - test_scenario::next_tx(scenario, ADMIN); - { - let admin_cap = test_scenario::take_from_sender>(scenario); - let mut lock = test_scenario::take_shared>(scenario); - - treasury_lock::ban_mint_cap_id( - &admin_cap, - &mut lock, - mint_cap_id - ); - - test_scenario::return_to_sender(scenario, admin_cap); - test_scenario::return_shared(lock); - }; - - // admin unbans mint cap - test_scenario::next_tx(scenario, ADMIN); - { - let admin_cap = test_scenario::take_from_sender>(scenario); - let mut lock = test_scenario::take_shared>(scenario); - - treasury_lock::unban_mint_cap_id( - &admin_cap, - &mut lock, - mint_cap_id - ); - - test_scenario::return_to_sender(scenario, admin_cap); - test_scenario::return_shared(lock); - }; - - // user can mint - test_scenario::next_tx(scenario, USER); - { - let balance = user_mint_balance(scenario, 100); - assert!(balance::value(&balance) == 100, 0); - - transfer::public_transfer( - coin::from_balance(balance, test_scenario::ctx(scenario)), - USER - ); - }; - test_scenario::end(scenario_); - } -} diff --git a/sui_programmability/examples/fungible_tokens/tests/basket_tests.move b/sui_programmability/examples/fungible_tokens/tests/basket_tests.move deleted file mode 100644 index 78fc04759d54e..0000000000000 --- a/sui_programmability/examples/fungible_tokens/tests/basket_tests.move +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module fungible_tokens::basket_tests { - use fungible_tokens::basket::{Self, Reserve}; - use fungible_tokens::managed::MANAGED; - use sui::pay; - use sui::coin; - use sui::sui::SUI; - use sui::test_scenario; - - #[test] - public fun test_mint_burn() { - let user = @0xA; - - let mut scenario_val = test_scenario::begin(user); - let scenario = &mut scenario_val; - { - let ctx = test_scenario::ctx(scenario); - basket::init_for_testing(ctx); - }; - test_scenario::next_tx(scenario, user); - { - let mut reserve_val = test_scenario::take_shared(scenario); - let reserve = &mut reserve_val; - let ctx = test_scenario::ctx(scenario); - assert!(basket::total_supply(reserve) == 0, 0); - - let num_coins = 10; - let sui = coin::mint_for_testing(num_coins, ctx); - let managed = coin::mint_for_testing(num_coins, ctx); - let basket = basket::mint(reserve, sui, managed, ctx); - assert!(coin::value(&basket) == num_coins, 1); - assert!(basket::total_supply(reserve) == num_coins, 2); - - let (sui, managed) = basket::burn(reserve, basket, ctx); - assert!(coin::value(&sui) == num_coins, 3); - assert!(coin::value(&managed) == num_coins, 4); - - pay::keep(sui, ctx); - pay::keep(managed, ctx); - test_scenario::return_shared(reserve_val); - }; - test_scenario::end(scenario_val); - } - -} diff --git a/sui_programmability/examples/games/Move.toml b/sui_programmability/examples/games/Move.toml deleted file mode 100644 index a8dad0c9d55b9..0000000000000 --- a/sui_programmability/examples/games/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "Games" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -games = "0x0" diff --git a/sui_programmability/examples/games/README.md b/sui_programmability/examples/games/README.md deleted file mode 100644 index 60fd8dc6c3985..0000000000000 --- a/sui_programmability/examples/games/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Games - -Examples of toy games built on top of Sui! - -* TicTacToe: the pencil and paper classic, now on Sui, implemented using single-owner objects only. -* SharedTicTacToe: the pencil and paper classic, now on Sui, using shared objects. -* Hero: an adventure game where an intrepid hero slays vicious boars with a magic sword and heals himself with potions. -* SeaHero: a permissionless mod of the Hero game where the hero can slay sea monsters to earn RUM tokens. -* SeaHeroHelper: a permissionless mod of the economics of the Sea Hero game. A weak hero can request help from a stronger hero, who receives a share of the monster slaying reward. -* RockPaperScissors: a commit-reveal scheme in which players first submit their commitments and then reveal the data that led to these commitments. -* DrandBasedLottery: a lottery game that depends on randomness from drand -* DrandBasedScratchCard: a scratch card game that depends on randomness from drand \ No newline at end of file diff --git a/sui_programmability/examples/games/sources/drand_based_lottery.move b/sui_programmability/examples/games/sources/drand_based_lottery.move deleted file mode 100644 index 26888dae98094..0000000000000 --- a/sui_programmability/examples/games/sources/drand_based_lottery.move +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// A basic game that depends on randomness from drand. -/// -/// The quicknet chain chain of drand creates random 32 bytes every 3 seconds. This randomness is verifiable in the sense -/// that anyone can check if a given 32 bytes bytes are indeed the i-th output of drand. For more details see -/// https://drand.love/ -/// -/// One could implement on-chain games that need unbiasable and unpredictable randomness using drand as the source of -/// randomness. I.e., every time the game needs randomness, it receives the next 32 bytes from drand (whether as part -/// of a transaction or by reading it from an existing object) and follows accordingly. -/// However, this simplistic flow may be insecure in some cases because the blockchain is not aware of the latest round -/// of drand, and thus it may depend on randomness that is already public. -/// -/// Below we design a game that overcomes this issue as following: -/// - The game is defined for a specific drand round N in the future, for example, the round that is expected in -/// 5 mins from now. -/// The current round for the main chain can be retrieved (off-chain) using -/// `curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest', -/// or using the following python script: -/// import time -/// genesis = 1692803367 -/// curr_round = (time.time() - genesis) // 3 + 1 -/// The round in 5 mins from now will be curr_round + 5*20. -/// (genesis is the epoch of the first round as returned from -/// curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/info.) -/// - Anyone can *close* the game to new participants by providing drand's randomness of round N-2 (i.e., 1 minute before -/// round N). The randomness of round X can be retrieved using -/// `curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/X'. -/// - Users can join the game as long as it is not closed and receive a *ticket*. -/// - Anyone can *complete* the game by proving drand's randomness of round N, which is used to declare the winner. -/// - The owner of the winning "ticket" can request a "winner ticket" and finish the game. -/// As long as someone is closing the game in time (or at least before round N) we have the guarantee that the winner is -/// selected using unpredictable and unbiasable randomness. Otherwise, someone could wait until the randomness of round N -/// is public, see if it could win the game and if so, join the game and drive it to completion. Therefore, honest users -/// are encouraged to close the game in time. -/// -/// All the external inputs needed for the following APIs can be retrieved from one of drand's public APIs, e.g. using -/// the above curl commands. -/// -module games::drand_based_lottery { - use games::drand_lib::{derive_randomness, verify_drand_signature, safe_selection}; - - - /// Error codes - const EGameNotInProgress: u64 = 0; - const EGameAlreadyCompleted: u64 = 1; - const EInvalidTicket: u64 = 3; - - /// Game status - const IN_PROGRESS: u8 = 0; - const CLOSED: u8 = 1; - const COMPLETED: u8 = 2; - - /// Game represents a set of parameters of a single game. - /// This game can be extended to require ticket purchase, reward winners, etc. - /// - public struct Game has key, store { - id: UID, - round: u64, - status: u8, - participants: u64, - winner: Option, - } - - /// Ticket represents a participant in a single game. - /// Can be deconstructed only by the owner. - public struct Ticket has key, store { - id: UID, - game_id: ID, - participant_index: u64, - } - - /// GameWinner represents a participant that won in a specific game. - /// Can be deconstructed only by the owner. - public struct GameWinner has key, store { - id: UID, - game_id: ID, - } - - /// Create a shared-object Game. - public entry fun create(round: u64, ctx: &mut TxContext) { - let game = Game { - id: object::new(ctx), - round, - status: IN_PROGRESS, - participants: 0, - winner: option::none(), - }; - transfer::public_share_object(game); - } - - /// Anyone can close the game by providing the randomness of round-2. - public entry fun close(game: &mut Game, drand_sig: vector) { - assert!(game.status == IN_PROGRESS, EGameNotInProgress); - verify_drand_signature(drand_sig, closing_round(game.round)); - game.status = CLOSED; - } - - /// Anyone can complete the game by providing the randomness of round. - public entry fun complete(game: &mut Game, drand_sig: vector) { - assert!(game.status != COMPLETED, EGameAlreadyCompleted); - verify_drand_signature(drand_sig, game.round); - game.status = COMPLETED; - // The randomness is derived from drand_sig by passing it through sha2_256 to make it uniform. - let digest = derive_randomness(drand_sig); - game.winner = option::some(safe_selection(game.participants, &digest)); - } - - /// Anyone can participate in the game and receive a ticket. - public entry fun participate(game: &mut Game, ctx: &mut TxContext) { - assert!(game.status == IN_PROGRESS, EGameNotInProgress); - let ticket = Ticket { - id: object::new(ctx), - game_id: object::id(game), - participant_index: game.participants, - }; - game.participants = game.participants + 1; - transfer::public_transfer(ticket, ctx.sender()); - } - - /// The winner can redeem its ticket. - public entry fun redeem(ticket: &Ticket, game: &Game, ctx: &mut TxContext) { - assert!(object::id(game) == ticket.game_id, EInvalidTicket); - assert!(game.winner.contains(&ticket.participant_index), EInvalidTicket); - - let winner = GameWinner { - id: object::new(ctx), - game_id: ticket.game_id, - }; - transfer::public_transfer(winner, ctx.sender()); - } - - // Note that a ticket can be deleted before the game was completed. - public entry fun delete_ticket(ticket: Ticket) { - let Ticket { id, game_id: _, participant_index: _} = ticket; - object::delete(id); - } - - public entry fun delete_game_winner(ticket: GameWinner) { - let GameWinner { id, game_id: _} = ticket; - object::delete(id); - } - - public fun get_ticket_game_id(ticket: &Ticket): &ID { - &ticket.game_id - } - - public fun get_game_winner_game_id(ticket: &GameWinner): &ID { - &ticket.game_id - } - - fun closing_round(round: u64): u64 { - round - 2 - } -} diff --git a/sui_programmability/examples/games/sources/drand_based_scratch_card.move b/sui_programmability/examples/games/sources/drand_based_scratch_card.move deleted file mode 100644 index 486d2073570df..0000000000000 --- a/sui_programmability/examples/games/sources/drand_based_scratch_card.move +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// A basic game that depends on randomness from drand (chained mode). See details on how to work with drand in -/// drand_based_lottery.move. -/// -/// Anyone can create a new game by depositing X*100 SUIs as a reward, and setting the current drand round as the base -/// round. This creates two objects: -/// - Game - an immutable object that includes all parameters to be used when buying tickets. -/// - Reward - a shared object that holds the reward. It can be withdrawn by any winner ("first come, first served"). -/// If not withdrawn within a few epochs, can be returned to the game creator. -/// -/// A user who wishes to play game G should: -/// - Check if G.epoch is the current epoch, and that G.base_drand_round + 24h is in the future. -/// See drand_based_lottery.move for how to calculate a round for a given point in time. -/// - Check that the relevant reward is still non negative. -/// - Call buy_ticket() with the right amount of SUI. -/// - Wait until the relevant drand round's randomness is available, and can call evaluate(). -/// - If received an object Winner, claim the reward using take_reward(). -/// -/// Important: There is 1 reward per game, and there is no limit on the number of tickets that can be bought for a -/// a single game. *Only* the winner of a game who called take_reward first will receive a reward. -/// One may extend this game and add another round in which winners can register their winner tickets, and then one of -/// the winners is chosen at random. This part, however, will require using a shared object. -/// -module games::drand_based_scratch_card { - use games::drand_lib; - use sui::balance::Balance; - use sui::coin::{Self, Coin}; - use sui::hmac::hmac_sha3_256; - - use sui::sui::SUI; - - /// Error codes - const EInvalidDeposit: u64 = 0; - const EInvalidEpoch: u64 = 1; - const EInvalidTicket: u64 = 2; - const EInvalidReward: u64 = 4; - const ETooSoonToRedeem: u64 = 5; - const EInvalidGame: u64 = 6; - - /// Game represents a set of parameters of a single game. - public struct Game has key { - id: UID, - creator: address, - reward_amount: u64, - reward_factor: u64, - base_epoch: u64, - base_drand_round: u64, - } - - /// Reward that is attached to a specific game. Can be withdrawn once. - public struct Reward has key { - id: UID, - game_id: ID, - balance: Balance, - } - - /// Ticket represents a participant in a single game. - /// Can be deconstructed only by the owner. - public struct Ticket has key, store { - id: UID, - game_id: ID, - } - - /// Winner represents a participant that won in a specific game. - /// Can be consumed by the take_reward. - public struct Winner has key, store { - id: UID, - game_id: ID, - } - - /// Create a new game with a given reward. - /// - /// The reward must be a positive balance, dividable by reward_factor. reward/reward_factor will be the ticket - /// price. base_drand_round is the current drand round. - public entry fun create( - reward: Coin, - reward_factor: u64, - base_drand_round: u64, - ctx: &mut TxContext - ) { - let amount = reward.value(); - assert!(amount > 0 && amount % reward_factor == 0 , EInvalidReward); - - let game = Game { - id: object::new(ctx), - reward_amount: reward.value(), - creator: ctx.sender(), - reward_factor, - base_epoch: ctx.epoch(), - base_drand_round, - }; - let reward = Reward { - id: object::new(ctx), - game_id: object::id(&game), - balance: reward.into_balance(), - }; - transfer::freeze_object(game); - transfer::share_object(reward); - } - - /// Buy a ticket for a specific game, costing reward/reward_factor SUI. Can be called only during the epoch in which - /// the game was created. - /// Note that the reward might have been withdrawn already. It's the user's responsibility to verify that. - public entry fun buy_ticket(coin: Coin, game: &Game, ctx: &mut TxContext) { - assert!(coin.value() * game.reward_factor == game.reward_amount, EInvalidDeposit); - assert!(ctx.epoch() == game.base_epoch, EInvalidEpoch); - let ticket = Ticket { - id: object::new(ctx), - game_id: object::id(game), - }; - transfer::public_transfer(coin, game.creator); - transfer::public_transfer(ticket, ctx.sender()); - } - - public entry fun evaluate( - ticket: Ticket, - game: &Game, - drand_sig: vector, - ctx: &mut TxContext - ) { - assert!(ticket.game_id == object::id(game), EInvalidTicket); - drand_lib::verify_drand_signature(drand_sig, end_of_game_round(game.base_drand_round)); - // The randomness for the current ticket is derived by HMAC(drand randomness, ticket id). - // A solution like checking if (drand randomness % reward_factor) == (ticket id % reward_factor) is not secure - // as the adversary can control the values of ticket id. (For this particular game this attack is not - // devastating, but for similar games it might be.) - let random_key = drand_lib::derive_randomness(drand_sig); - let randomness = hmac_sha3_256(&random_key, &object::id(&ticket).to_bytes()); - let is_winner = (drand_lib::safe_selection(game.reward_factor, &randomness) == 0); - - if (is_winner) { - let winner = Winner { - id: object::new(ctx), - game_id: object::id(game), - }; - transfer::public_transfer(winner, ctx.sender()); - }; - // Delete the ticket. - let Ticket { id, game_id: _} = ticket; - object::delete(id); - } - - public entry fun take_reward(winner: Winner, reward: &mut Reward, ctx: &mut TxContext) { - assert!(winner.game_id == reward.game_id, EInvalidTicket); - let full_balance = reward.balance.value(); - if (full_balance > 0) { - transfer::public_transfer(coin::take(&mut reward.balance, full_balance, ctx), ctx.sender()); - }; - let Winner { id, game_id: _} = winner; - object::delete(id); - } - - /// Can be called in case the reward was not withdrawn, to return the coins to the creator. - public entry fun redeem(reward: &mut Reward, game: &Game, ctx: &mut TxContext) { - assert!(reward.balance.value() > 0, EInvalidReward); - assert!(object::id(game) == reward.game_id, EInvalidGame); - // Since we define the game to take 24h+25h, a game that is created in epoch x may be completed in epochs - // x+2 or x+3. - assert!(game.base_epoch + 3 < ctx.epoch(), ETooSoonToRedeem); - let full_balance = reward.balance.value(); - transfer::public_transfer(coin::take(&mut reward.balance, full_balance, ctx), game.creator); - } - - public entry fun delete_ticket(ticket: Ticket) { - let Ticket { id, game_id: _} = ticket; - object::delete(id); - } - - public fun get_game_base_drand_round(game: &Game): u64 { - game.base_drand_round - } - - public fun get_game_base_epoch(game: &Game): u64 { - game.base_epoch - } - - public fun end_of_game_round(round: u64): u64 { - // Since users do not know when an epoch has began, they can only check if the game depends on a round that is - // at least 24 hours from now. Since the creator does not know as well if its game is created in the beginning - // or the end of the epoch, we define the end of the game to be 24h + 24h from when it started, +1h to be on - // the safe side since epoch duration is not deterministic. - round + 20 * 60 * (24 + 25) - } -} diff --git a/sui_programmability/examples/games/sources/drand_lib.move b/sui_programmability/examples/games/sources/drand_lib.move deleted file mode 100644 index 2ab8986514ef6..0000000000000 --- a/sui_programmability/examples/games/sources/drand_lib.move +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Helper module for working with drand outputs. -/// Currently works with chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971 (quicknet). -/// -/// See examples of how to use this in drand_based_lottery.move and drand_based_scratch_card.move. -/// -/// If you want to use this module with the default network which has a 30s period, you need to change the public key, -/// genesis time and include the previous signature in verify_drand_signature. See https://drand.love/developer/ or the -/// previous version of this file: https://github.com/MystenLabs/sui/blob/92df778310679626f00bc4226d7f7a281322cfdd/sui_programmability/examples/games/sources/drand_lib.move -module games::drand_lib { - use std::hash::sha2_256; - - use sui::bls12381; - - /// Error codes - const EInvalidRndLength: u64 = 0; - const EInvalidProof: u64 = 1; - - /// The genesis time of chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971. - const GENESIS: u64 = 1692803367; - /// The public key of chain 52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971. - const DRAND_PK: vector = - x"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a"; - - /// The time in seconds between randomness beacon rounds. - const PERIOD: u64 = 3; - - /// Check that a given epoch time has passed by verifying a drand signature from a later time. - /// round must be at least (epoch_time - GENESIS)/PERIOD + 1). - public fun verify_time_has_passed(epoch_time: u64, sig: vector, round: u64) { - assert!(epoch_time <= GENESIS + PERIOD * (round - 1), EInvalidProof); - verify_drand_signature(sig, round); - } - - /// Check a drand output. - public fun verify_drand_signature(sig: vector, mut round: u64) { - // Convert round to a byte array in big-endian order. - let mut round_bytes: vector = vector[0, 0, 0, 0, 0, 0, 0, 0]; - let mut i = 7; - - // Note that this loop never copies the last byte of round_bytes, though it is not expected to ever be non-zero. - while (i > 0) { - let curr_byte = round % 0x100; - let curr_element = &mut round_bytes[i]; - *curr_element = (curr_byte as u8); - round = round >> 8; - i = i - 1; - }; - - // Compute sha256(prev_sig, round_bytes). - let digest = sha2_256(round_bytes); - // Verify the signature on the hash. - let drand_pk = DRAND_PK; - assert!(bls12381::bls12381_min_sig_verify(&sig, &drand_pk, &digest), EInvalidProof); - } - - /// Derive a uniform vector from a drand signature. - public fun derive_randomness(drand_sig: vector): vector { - sha2_256(drand_sig) - } - - // Converts the first 16 bytes of rnd to a u128 number and outputs its modulo with input n. - // Since n is u64, the output is at most 2^{-64} biased assuming rnd is uniformly random. - public fun safe_selection(n: u64, rnd: &vector): u64 { - assert!(rnd.length() >= 16, EInvalidRndLength); - let mut m: u128 = 0; - let mut i = 0; - while (i < 16) { - m = m << 8; - let curr_byte = rnd[i]; - m = m + (curr_byte as u128); - i = i + 1; - }; - let n_128 = (n as u128); - let module_128 = m % n_128; - let res = (module_128 as u64); - res - } -} diff --git a/sui_programmability/examples/games/sources/hero.move b/sui_programmability/examples/games/sources/hero.move deleted file mode 100644 index a350b7e053de0..0000000000000 --- a/sui_programmability/examples/games/sources/hero.move +++ /dev/null @@ -1,401 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Example of a game character with basic attributes, inventory, and -/// associated logic. -module games::hero { - use sui::coin::Coin; - use sui::event; - use sui::sui::SUI; - - /// Our hero! - public struct Hero has key, store { - id: UID, - /// Hit points. If they go to zero, the hero can't do anything - hp: u64, - /// Experience of the hero. Begins at zero - experience: u64, - /// The hero's minimal inventory - sword: Option, - /// An ID of the game user is playing - game_id: ID, - } - - /// The hero's trusty sword - public struct Sword has key, store { - id: UID, - /// Constant set at creation. Acts as a multiplier on sword's strength. - /// Swords with high magic are rarer (because they cost more). - magic: u64, - /// Sword grows in strength as we use it - strength: u64, - /// An ID of the game - game_id: ID, - } - - /// For healing wounded heroes - public struct Potion has key, store { - id: UID, - /// Effectiveness of the potion - potency: u64, - /// An ID of the game - game_id: ID, - } - - /// A creature that the hero can slay to level up - public struct Boar has key { - id: UID, - /// Hit points before the boar is slain - hp: u64, - /// Strength of this particular boar - strength: u64, - /// An ID of the game - game_id: ID, - } - - /// An immutable object that contains information about the - /// game admin. Created only once in the module initializer, - /// hence it cannot be recreated or falsified. - public struct GameInfo has key { - id: UID, - admin: address - } - - /// Capability conveying the authority to create boars and potions - public struct GameAdmin has key { - id: UID, - /// Total number of boars the admin has created - boars_created: u64, - /// Total number of potions the admin has created - potions_created: u64, - /// ID of the game where current user is an admin - game_id: ID, - } - - /// Event emitted each time a Hero slays a Boar - public struct BoarSlainEvent has copy, drop { - /// Address of the user that slayed the boar - slayer_address: address, - /// ID of the Hero that slayed the boar - hero: ID, - /// ID of the now-deceased boar - boar: ID, - /// ID of the game where event happened - game_id: ID, - } - - /// Upper bound on player's HP - const MAX_HP: u64 = 1000; - /// Upper bound on how magical a sword can be - const MAX_MAGIC: u64 = 10; - /// Minimum amount you can pay for a sword - const MIN_SWORD_COST: u64 = 100; - - // TODO: proper error codes - /// The boar won the battle - const EBOAR_WON: u64 = 0; - /// Not enough money to purchase the given item - const EINSUFFICIENT_FUNDS: u64 = 3; - /// Trying to remove a sword, but the hero does not have one - const ENO_SWORD: u64 = 4; - /// Assertion errors for testing - const ASSERT_ERR: u64 = 5; - - // --- Initialization - - #[allow(unused_function)] - /// On module publish, sender creates a new game. But once it is published, - /// anyone create a new game with a `new_game` function. - fun init(ctx: &mut TxContext) { - create(ctx); - } - - /// Anyone can create run their own game, all game objects will be - /// linked to this game. - public entry fun new_game(ctx: &mut TxContext) { - create(ctx); - } - - /// Create a new game. Separated to bypass public entry vs init requirements. - fun create(ctx: &mut TxContext) { - let sender = ctx.sender(); - let id = object::new(ctx); - let game_id = id.to_inner(); - - transfer::freeze_object(GameInfo { - id, - admin: sender, - }); - - transfer::transfer( - GameAdmin { - game_id, - id: object::new(ctx), - boars_created: 0, - potions_created: 0, - }, - sender - ) - } - - // --- Gameplay --- - - /// Slay the `boar` with the `hero`'s sword, get experience. - /// Aborts if the hero has 0 HP or is not strong enough to slay the boar - public entry fun slay( - game: &GameInfo, hero: &mut Hero, boar: Boar, ctx: &TxContext - ) { - game.check_id(hero.game_id); - game.check_id(boar.game_id); - let Boar { id: boar_id, strength: boar_strength, hp, game_id: _ } = boar; - let hero_strength = hero_strength(hero); - let mut boar_hp = hp; - let mut hero_hp = hero.hp; - // attack the boar with the sword until its HP goes to zero - while (boar_hp > hero_strength) { - // first, the hero attacks - boar_hp = boar_hp - hero_strength; - // then, the boar gets a turn to attack. if the boar would kill - // the hero, abort--we can't let the boar win! - assert!(hero_hp >= boar_strength , EBOAR_WON); - hero_hp = hero_hp - boar_strength; - - }; - // hero takes their licks - hero.hp = hero_hp; - // hero gains experience proportional to the boar, sword grows in - // strength by one (if hero is using a sword) - hero.experience = hero.experience + hp; - if (hero.sword.is_some()) { - hero.sword.borrow_mut().level_up(1) - }; - // let the world know about the hero's triumph by emitting an event! - event::emit(BoarSlainEvent { - slayer_address: ctx.sender(), - hero: hero.id.to_inner(), - boar: boar_id.to_inner(), - game_id: id(game) - }); - object::delete(boar_id); - } - - public use fun hero_strength as Hero.strength; - - /// Strength of the hero when attacking - public fun hero_strength(hero: &Hero): u64 { - // a hero with zero HP is too tired to fight - if (hero.hp == 0) { - return 0 - }; - - let sword_strength = if (hero.sword.is_some()) { - hero.sword.borrow().strength() - } else { - // hero can fight without a sword, but will not be very strong - 0 - }; - // hero is weaker if he has lower HP - (hero.experience * hero.hp) + sword_strength - } - - use fun level_up_sword as Sword.level_up; - - fun level_up_sword(sword: &mut Sword, amount: u64) { - sword.strength = sword.strength + amount - } - - public use fun sword_strength as Sword.strength; - - /// Strength of a sword when attacking - public fun sword_strength(sword: &Sword): u64 { - sword.magic + sword.strength - } - - // --- Inventory --- - - /// Heal the weary hero with a potion - public fun heal(hero: &mut Hero, potion: Potion) { - assert!(hero.game_id == potion.game_id, 403); - let Potion { id, potency, game_id: _ } = potion; - object::delete(id); - let new_hp = hero.hp + potency; - // cap hero's HP at MAX_HP to avoid int overflows - hero.hp = new_hp.min(MAX_HP) - } - - /// Add `new_sword` to the hero's inventory and return the old sword - /// (if any) - public fun equip_sword(hero: &mut Hero, new_sword: Sword): Option { - hero.sword.swap_or_fill(new_sword) - } - - /// Disarm the hero by returning their sword. - /// Aborts if the hero does not have a sword. - public fun remove_sword(hero: &mut Hero): Sword { - assert!(hero.sword.is_some(), ENO_SWORD); - hero.sword.extract() - } - - // --- Object creation --- - - /// It all starts with the sword. Anyone can buy a sword, and proceeds go - /// to the admin. Amount of magic in the sword depends on how much you pay - /// for it. - public fun create_sword( - game: &GameInfo, - payment: Coin, - ctx: &mut TxContext - ): Sword { - let value = payment.value(); - // ensure the user pays enough for the sword - assert!(value >= MIN_SWORD_COST, EINSUFFICIENT_FUNDS); - // pay the admin for this sword - transfer::public_transfer(payment, game.admin); - - // magic of the sword is proportional to the amount you paid, up to - // a max. one can only imbue a sword with so much magic - let magic = (value - MIN_SWORD_COST) / MIN_SWORD_COST; - Sword { - id: object::new(ctx), - magic: magic.min(MAX_MAGIC), - strength: 1, - game_id: id(game) - } - } - - public entry fun acquire_hero( - game: &GameInfo, payment: Coin, ctx: &mut TxContext - ) { - let sword = game.create_sword(payment, ctx); - let hero = game.create_hero(sword, ctx); - transfer::public_transfer(hero, ctx.sender()) - } - - /// Anyone can create a hero if they have a sword. All heroes start with the - /// same attributes. - public fun create_hero( - game: &GameInfo, sword: Sword, ctx: &mut TxContext - ): Hero { - game.check_id(sword.game_id); - Hero { - id: object::new(ctx), - hp: 100, - experience: 0, - sword: option::some(sword), - game_id: id(game) - } - } - - /// Admin can create a potion with the given `potency` for `recipient` - public entry fun send_potion( - game: &GameInfo, - potency: u64, - player: address, - admin: &mut GameAdmin, - ctx: &mut TxContext - ) { - game.check_id(admin.game_id); - admin.potions_created = admin.potions_created + 1; - // send potion to the designated player - transfer::public_transfer( - Potion { id: object::new(ctx), potency, game_id: id(game) }, - player - ) - } - - /// Admin can create a boar with the given attributes for `recipient` - public entry fun send_boar( - game: &GameInfo, - admin: &mut GameAdmin, - hp: u64, - strength: u64, - player: address, - ctx: &mut TxContext - ) { - game.check_id(admin.game_id); - admin.boars_created = admin.boars_created + 1; - // send boars to the designated player - transfer::transfer( - Boar { id: object::new(ctx), hp, strength, game_id: id(game) }, - player - ) - } - - // --- Game integrity / Links checks --- - - public fun check_id(game_info: &GameInfo, id: ID) { - assert!(game_info.id() == id, 403); // TODO: error code - } - - public fun id(game_info: &GameInfo): ID { - object::id(game_info) - } - - // --- Testing functions --- - public fun assert_hero_strength(hero: &Hero, strength: u64) { - assert!(hero.strength() == strength, ASSERT_ERR); - } - - #[test_only] - public fun delete_hero_for_testing(hero: Hero) { - let Hero { id, hp: _, experience: _, sword, game_id: _ } = hero; - object::delete(id); - let sword = sword.destroy_some(); - let Sword { id, magic: _, strength: _, game_id: _ } = sword; - object::delete(id) - } - - #[test_only] - public fun delete_game_admin_for_testing(admin: GameAdmin) { - let GameAdmin { id, boars_created: _, potions_created: _, game_id: _ } = admin; - object::delete(id); - } - - #[test] - fun slay_boar_test() { - use sui::test_scenario; - use sui::coin; - - let admin = @0xAD014; - let player = @0x0; - - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - // Run the module initializers - scenario.next_tx(admin); - { - init(scenario.ctx()); - }; - // Player purchases a hero with the coins - scenario.next_tx(player); - { - let game = scenario.take_immutable(); - let game_ref = &game; - let coin = coin::mint_for_testing(500, scenario.ctx()); - acquire_hero(game_ref, coin, scenario.ctx()); - test_scenario::return_immutable(game); - }; - // Admin sends a boar to the Player - scenario.next_tx(admin); - { - let game = scenario.take_immutable(); - let game_ref = &game; - let mut admin_cap = scenario.take_from_sender(); - send_boar(game_ref, &mut admin_cap, 10, 10, player, scenario.ctx()); - scenario.return_to_sender(admin_cap); - test_scenario::return_immutable(game); - }; - // Player slays the boar! - scenario.next_tx(player); - { - let game = scenario.take_immutable(); - let game_ref = &game; - let mut hero = scenario.take_from_sender(); - let boar = scenario.take_from_sender(); - slay(game_ref, &mut hero, boar, scenario.ctx()); - scenario.return_to_sender(hero); - test_scenario::return_immutable(game); - }; - scenario_val.end(); - } -} diff --git a/sui_programmability/examples/games/sources/rock_paper_scissors.move b/sui_programmability/examples/games/sources/rock_paper_scissors.move deleted file mode 100644 index 6e33fda37b429..0000000000000 --- a/sui_programmability/examples/games/sources/rock_paper_scissors.move +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// This is an idea of a module which will allow some asset to be -// won by playing a rock-paper-scissors (then lizard-spoke) game. -// -// Initial implementation implies so-called commit-reveal scheme -// in which players first submit their commitments -// and then reveal the data that led to these commitments. The -// data is then being verified by one of the parties or a third -// party (depends on implementation and security measures). -// -// In this specific example, the flow is: -// 1. User A creates a Game struct, where he puts a prize asset -// 2. Both users B and C submit their hashes to the game as their -// guesses but don't reveal the actual values yet -// 3. Users B and C submit their salts, so the user A -// can see and prove that the values match, and decides who won the -// round. Asset is then released to the winner or to the game owner -// if nobody won. -// -// TODO: -// - Error codes -// - Status checks -// - If player never revealed the secret -// - If game owner never took or revealed the results (incentives?) - -module games::rock_paper_scissors { - use std::hash; - - // -- Gestures and additional consts -- // - - const NONE: u8 = 0; - const ROCK: u8 = 1; - const PAPER: u8 = 2; - const SCISSORS: u8 = 3; - const CHEAT: u8 = 111; - - public fun rock(): u8 { ROCK } - public fun paper(): u8 { PAPER } - public fun scissors(): u8 { SCISSORS } - - // -- Game statuses list -- // - - const STATUS_READY: u8 = 0; - const STATUS_HASH_SUBMISSION: u8 = 1; - const STATUS_HASHES_SUBMITTED: u8 = 2; - const STATUS_REVEALING: u8 = 3; - const STATUS_REVEALED: u8 = 4; - - /// The Prize that's being held inside the [`Game`] object. Should be - /// eventually replaced with some generic T inside the [`Game`]. - public struct ThePrize has key, store { - id: UID - } - - /// The main resource of the rock_paper_scissors module. Contains all the - /// information about the game state submitted by both players. By default - /// contains empty values and fills as the game progresses. - /// Being destroyed in the end, once [`select_winner`] is called and the game - /// has reached its final state by that time. - public struct Game has key { - id: UID, - prize: ThePrize, - player_one: address, - player_two: address, - hash_one: vector, - hash_two: vector, - gesture_one: u8, - gesture_two: u8, - } - - /// Hashed gesture. It is not reveal-able until both players have - /// submitted their moves to the Game. The turn is passed to the - /// game owner who then adds a hash to the Game object. - public struct PlayerTurn has key { - id: UID, - hash: vector, - player: address, - } - - /// Secret object which is used to reveal the move. Just like [`PlayerTurn`] - /// it is used to reveal the actual gesture a player has submitted. - public struct Secret has key { - id: UID, - salt: vector, - player: address, - } - - /// Shows the current game status. This function is also used in the [`select_winner`] - /// entry point and limits the ability to select a winner, if one of the secrets hasn't - /// been revealed yet. - public fun status(game: &Game): u8 { - let h1_len = game.hash_one.length(); - let h2_len = game.hash_two.length(); - - if (game.gesture_one != NONE && game.gesture_two != NONE) { - STATUS_REVEALED - } else if (game.gesture_one != NONE || game.gesture_two != NONE) { - STATUS_REVEALING - } else if (h1_len == 0 && h2_len == 0) { - STATUS_READY - } else if (h1_len != 0 && h2_len != 0) { - STATUS_HASHES_SUBMITTED - } else if (h1_len != 0 || h2_len != 0) { - STATUS_HASH_SUBMISSION - } else { - 0 - } - } - - /// Start a new game at sender address. The only arguments needed are players, the rest - /// is initiated with default/empty values which will be filled later in the game. - /// - /// todo: extend with generics + T as prize - public entry fun new_game(player_one: address, player_two: address, ctx: &mut TxContext) { - transfer::transfer(Game { - id: object::new(ctx), - prize: ThePrize { id: object::new(ctx) }, - player_one, - player_two, - hash_one: vector[], - hash_two: vector[], - gesture_one: NONE, - gesture_two: NONE, - }, ctx.sender()); - } - - /// Transfer [`PlayerTurn`] to the game owner. Nobody at this point knows what move - /// is encoded inside the [`hash`] argument. - /// - /// Currently there's no check on whether the game exists. - public entry fun player_turn(at: address, hash: vector, ctx: &mut TxContext) { - transfer::transfer(PlayerTurn { - hash, - id: object::new(ctx), - player: ctx.sender(), - }, at); - } - - /// Add a hashed gesture to the game. Store it as a `hash_one` or `hash_two` depending - /// on the player number (one or two) - public entry fun add_hash(game: &mut Game, cap: PlayerTurn) { - let PlayerTurn { hash, id, player } = cap; - let status = status(game); - - assert!(status == STATUS_HASH_SUBMISSION || status == STATUS_READY, 0); - assert!(game.player_one == player || game.player_two == player, 0); - - if (player == game.player_one && game.hash_one.length() == 0) { - game.hash_one = hash; - } else if (player == game.player_two && game.hash_two.length() == 0) { - game.hash_two = hash; - } else { - abort 0 // unreachable!() - }; - - object::delete(id); - } - - /// Submit a [`Secret`] to the game owner who then matches the hash and saves the - /// gesture in the [`Game`] object. - public entry fun reveal(at: address, salt: vector, ctx: &mut TxContext) { - transfer::transfer(Secret { - id: object::new(ctx), - salt, - player: ctx.sender(), - }, at); - } - - /// Use submitted [`Secret`]'s salt to find the gesture played by the player and set it - /// in the [`Game`] object. - /// TODO: think of ways to - public entry fun match_secret(game: &mut Game, secret: Secret) { - let Secret { salt, player, id } = secret; - - assert!(player == game.player_one || player == game.player_two, 0); - - if (player == game.player_one) { - game.gesture_one = find_gesture(salt, &game.hash_one); - } else if (player == game.player_two) { - game.gesture_two = find_gesture(salt, &game.hash_two); - }; - - object::delete(id); - } - - /// The final accord to the game logic. After both secrets have been revealed, - /// the game owner can choose a winner and release the prize. - public entry fun select_winner(game: Game, ctx: &TxContext) { - assert!(status(&game) == STATUS_REVEALED, 0); - - let Game { - id, - prize, - player_one, - player_two, - hash_one: _, - hash_two: _, - gesture_one, - gesture_two, - } = game; - - let p1_wins = play(gesture_one, gesture_two); - let p2_wins = play(gesture_two, gesture_one); - - object::delete(id); - - // If one of the players wins, he takes the prize. - // If there's a tie, the game owner gets the prize. - if (p1_wins) { - transfer::public_transfer(prize, player_one) - } else if (p2_wins) { - transfer::public_transfer(prize, player_two) - } else { - transfer::public_transfer(prize, ctx.sender()) - }; - } - - /// Implement the basic logic of the game. - fun play(one: u8, two: u8): bool { - if (one == ROCK && two == SCISSORS) { true } - else if (one == PAPER && two == ROCK) { true } - else if (one == SCISSORS && two == PAPER) { true } - else if (one != CHEAT && two == CHEAT) { true } - else { false } - } - - /// Hash the salt and the gesture_id and match it against the stored hash. If something - /// matches, the gesture_id is returned, if nothing - player is considered a cheater, and - /// he automatically loses the round. - fun find_gesture(salt: vector, hash: &vector): u8 { - if (hash(ROCK, salt) == *hash) { - ROCK - } else if (hash(PAPER, salt) == *hash) { - PAPER - } else if (hash(SCISSORS, salt) == *hash) { - SCISSORS - } else { - CHEAT - } - } - - /// Internal hashing function to build a [`Secret`] and match it later at the reveal stage. - /// - /// - `salt` argument here is a secret that is only known to the sender. That way we ensure - /// that nobody knows the gesture until the end, but at the same time each player commits - /// to the result with his hash; - fun hash(gesture: u8, mut salt: vector): vector { - salt.push_back(gesture); - hash::sha2_256(salt) - } -} diff --git a/sui_programmability/examples/games/sources/sea_hero.move b/sui_programmability/examples/games/sources/sea_hero.move deleted file mode 100644 index 46236192a5773..0000000000000 --- a/sui_programmability/examples/games/sources/sea_hero.move +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Example of a game mod or different game that uses objects from the Hero -/// game. -/// This mod introduces sea monsters that can also be slain with the hero's -/// sword. Instead of boosting the hero's experience, slaying sea monsters -/// earns RUM tokens for hero's owner. -/// Note that this mod does not require special permissions from `Hero` module; -/// anyone is free to create a mod like this. -module games::sea_hero { - use games::hero::Hero; - - use sui::balance::{Self, Balance, Supply}; - - /// Admin capability granting permission to mint RUM tokens and - /// create monsters - public struct SeaHeroAdmin has key { - id: UID, - /// Permission to mint RUM - supply: Supply, - /// Total number of monsters created so far - monsters_created: u64, - /// cap on the supply of RUM - token_supply_max: u64, - /// cap on the number of monsters that can be created - monster_max: u64 - } - - /// A new kind of monster for the hero to fight - public struct SeaMonster has key, store { - id: UID, - /// Tokens that the user will earn for slaying this monster - reward: Balance - } - - /// Type of the sea game token - public struct RUM has drop {} - - // TODO: proper error codes - /// Hero is not strong enough to defeat the monster. Try healing with a - /// potion, fighting boars to gain more experience, or getting a better - /// sword - const EHERO_NOT_STRONG_ENOUGH: u64 = 0; - - // --- Initialization --- - - - - #[allow(unused_function)] - /// Get a treasury cap for the coin and give it to the admin - // TODO: this leverages Move module initializers - fun init(ctx: &mut TxContext) { - transfer::transfer( - SeaHeroAdmin { - id: object::new(ctx), - supply: balance::create_supply(RUM {}), - monsters_created: 0, - token_supply_max: 1000000, - monster_max: 10, - }, - ctx.sender() - ) - } - - // --- Gameplay --- - - /// Slay the `monster` with the `hero`'s sword, earn RUM tokens in - /// exchange. - /// Aborts if the hero is not strong enough to slay the monster - public fun slay(hero: &Hero, monster: SeaMonster): Balance { - let SeaMonster { id, reward } = monster; - object::delete(id); - // Hero needs strength greater than the reward value to defeat the - // monster - assert!( - hero.strength() >= reward.value(), - EHERO_NOT_STRONG_ENOUGH - ); - - reward - } - - // --- Object and coin creation --- - - /// Game admin can create a monster wrapping a coin worth `reward` and send - /// it to `recipient` - public entry fun create_monster( - admin: &mut SeaHeroAdmin, - reward_amount: u64, - recipient: address, - ctx: &mut TxContext - ) { - let current_coin_supply = admin.supply.supply_value(); - let token_supply_max = admin.token_supply_max; - // TODO: create error codes - // ensure token supply cap is respected - assert!(reward_amount < token_supply_max, 0); - assert!(token_supply_max - reward_amount >= current_coin_supply, 1); - // ensure monster supply cap is respected - assert!(admin.monster_max - 1 >= admin.monsters_created, 2); - - let monster = SeaMonster { - id: object::new(ctx), - reward: admin.supply.increase_supply(reward_amount), - }; - admin.monsters_created = admin.monsters_created + 1; - - transfer::public_transfer(monster, recipient) - } - - /// Reward a hero will reap from slaying this monster - public fun monster_reward(monster: &SeaMonster): u64 { - monster.reward.value() - } -} diff --git a/sui_programmability/examples/games/sources/sea_hero_helper.move b/sui_programmability/examples/games/sources/sea_hero_helper.move deleted file mode 100644 index 2c74e8332700d..0000000000000 --- a/sui_programmability/examples/games/sources/sea_hero_helper.move +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Mod of the economics of the SeaHero game. In the game, a `Hero` can only -/// slay a `SeaMonster` if they have sufficient strength. This mod allows a -/// player with a weak `Hero` to ask a player with a stronger `Hero` to slay -/// the monster for them in exchange for some of the reward. -/// Anyone can create a mod like this--the permission of the `SeaHero` game -/// is not required. -module games::sea_hero_helper { - use games::sea_hero::{Self, SeaMonster, RUM}; - use games::hero::Hero; - use sui::coin::{Self, Coin}; - - /// Created by `monster_owner`, a player with a monster that's too strong - /// for them to slay + transferred to a player who can slay the monster. - /// The two players split the reward for slaying the monster according to - /// the `helper_reward` parameter. - public struct HelpMeSlayThisMonster has key { - id: UID, - /// Monster to be slay by the owner of this object - monster: SeaMonster, - /// Identity of the user that originally owned the monster - monster_owner: address, - /// Number of tokens that will go to the helper. The owner will get - /// the `monster` reward - `helper_reward` tokens - helper_reward: u64, - } - - // TODO: proper error codes - /// The specified helper reward is too large - const EINVALID_HELPER_REWARD: u64 = 0; - - /// Create an offer for `helper` to slay the monster in exchange for - /// some of the reward - public fun create( - monster: SeaMonster, - helper_reward: u64, - helper: address, - ctx: &mut TxContext, - ) { - // make sure the advertised reward is not too large + that the owner - // gets a nonzero reward - assert!( - monster.monster_reward() > helper_reward, - EINVALID_HELPER_REWARD - ); - transfer::transfer( - HelpMeSlayThisMonster { - id: object::new(ctx), - monster, - monster_owner: ctx.sender(), - helper_reward - }, - helper - ) - } - - /// Helper should call this if they are willing to help out and slay the - /// monster. - public fun slay( - hero: &Hero, wrapper: HelpMeSlayThisMonster, ctx: &mut TxContext, - ): Coin { - let HelpMeSlayThisMonster { - id, - monster, - monster_owner, - helper_reward - } = wrapper; - object::delete(id); - let mut owner_reward = sea_hero::slay(hero, monster); - let helper_reward = coin::take(&mut owner_reward, helper_reward, ctx); - transfer::public_transfer(owner_reward.into_coin(ctx), monster_owner); - helper_reward - } - - /// Helper can call this if they can't help slay the monster or don't want - /// to, and are willing to kindly return the monster to its owner. - public fun return_to_owner(wrapper: HelpMeSlayThisMonster) { - let HelpMeSlayThisMonster { - id, - monster, - monster_owner, - helper_reward: _ - } = wrapper; - object::delete(id); - transfer::public_transfer(monster, monster_owner) - } - - /// Return the number of coins that `wrapper.owner` will earn if the - /// the helper slays the monster in `wrapper. - public fun owner_reward(wrapper: &HelpMeSlayThisMonster): u64 { - wrapper.monster.monster_reward() - wrapper.helper_reward - } -} diff --git a/sui_programmability/examples/games/sources/shared_tic_tac_toe.move b/sui_programmability/examples/games/sources/shared_tic_tac_toe.move deleted file mode 100644 index 4e601dca40f59..0000000000000 --- a/sui_programmability/examples/games/sources/shared_tic_tac_toe.move +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// This is a rewrite of TicTacToe using a completely different approach. -// In TicTacToe, since the game object is owned by the admin, the players are not -// able to directly mutate the gameboard. Hence each marker placement takes -// two transactions. -// In this implementation, we make the game object a shared object. -// Both players have access and can mutate the game object, and hence they -// can place markers directly in one transaction. -// In general, using shared object has an extra cost due to the fact -// that Sui needs to sequence the operations that mutate the shared object from -// different transactions. In this case however, since it is expected for players -// to take turns to place the marker, there won't be a significant overhead in practice. -// As we can see, by using shared object, the implementation is much -// simpler than the other implementation. -module games::shared_tic_tac_toe { - use sui::event; - - // Game status - const IN_PROGRESS: u8 = 0; - const X_WIN: u8 = 1; - const O_WIN: u8 = 2; - const DRAW: u8 = 3; - const FINAL_TURN: u8 = 8; - - - // Mark type - const MARK_EMPTY: u8 = 2; - - // Error codes - /// Trying to place a mark when it's not your turn. - const EInvalidTurn: u64 = 0; - /// Trying to place a mark when the game has already ended. - const EGameEnded: u64 = 1; - /// Trying to place a mark in an invalid location, i.e. row/column out of bound. - const EInvalidLocation: u64 = 2; - /// The cell to place a new mark at is already oocupied. - const ECellOccupied: u64 = 3; - - public struct TicTacToe has key { - id: UID, - gameboard: vector>, - cur_turn: u8, - game_status: u8, - x_address: address, - o_address: address, - } - - public struct Trophy has key { - id: UID, - } - - public struct GameEndEvent has copy, drop { - // The Object ID of the game object - game_id: ID, - } - - /// `x_address` and `o_address` are the account address of the two players. - public entry fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { - // TODO: Validate sender address, only GameAdmin can create games. - - let id = object::new(ctx); - let gameboard = vector[ - vector[MARK_EMPTY, MARK_EMPTY, MARK_EMPTY], - vector[MARK_EMPTY, MARK_EMPTY, MARK_EMPTY], - vector[MARK_EMPTY, MARK_EMPTY, MARK_EMPTY], - ]; - let game = TicTacToe { - id, - gameboard, - cur_turn: 0, - game_status: IN_PROGRESS, - x_address: x_address, - o_address: o_address, - }; - // Make the game a shared object so that both players can mutate it. - transfer::share_object(game); - } - - public entry fun place_mark(game: &mut TicTacToe, row: u8, col: u8, ctx: &mut TxContext) { - assert!(row < 3 && col < 3, EInvalidLocation); - assert!(game.game_status == IN_PROGRESS, EGameEnded); - let addr = game.get_cur_turn_address(); - assert!(addr == ctx.sender(), EInvalidTurn); - - let cell = &mut game.gameboard[row as u64][col as u64]; - assert!(*cell == MARK_EMPTY, ECellOccupied); - - *cell = game.cur_turn % 2; - game.update_winner(); - game.cur_turn = game.cur_turn + 1; - - if (game.game_status != IN_PROGRESS) { - // Notify the server that the game ended so that it can delete the game. - event::emit(GameEndEvent { game_id: object::id(game) }); - if (game.game_status == X_WIN) { - transfer::transfer(Trophy { id: object::new(ctx) }, game.x_address); - } else if (game.game_status == O_WIN) { - transfer::transfer(Trophy { id: object::new(ctx) }, game.o_address); - } - } - } - - public entry fun delete_game(game: TicTacToe) { - let TicTacToe { id, gameboard: _, cur_turn: _, game_status: _, x_address: _, o_address: _ } = game; - object::delete(id); - } - - public entry fun delete_trophy(trophy: Trophy) { - let Trophy { id } = trophy; - object::delete(id); - } - - public fun get_status(game: &TicTacToe): u8 { - game.game_status - } - - fun get_cur_turn_address(game: &TicTacToe): address { - if (game.cur_turn % 2 == 0) { - game.x_address - } else { - game.o_address - } - } - - fun get_cell(game: &TicTacToe, row: u64, col: u64): u8 { - game.gameboard[row][col] - } - - fun update_winner(game: &mut TicTacToe) { - // Check all rows - check_for_winner(game, 0, 0, 0, 1, 0, 2); - check_for_winner(game, 1, 0, 1, 1, 1, 2); - check_for_winner(game, 2, 0, 2, 1, 2, 2); - - // Check all columns - check_for_winner(game, 0, 0, 1, 0, 2, 0); - check_for_winner(game, 0, 1, 1, 1, 2, 1); - check_for_winner(game, 0, 2, 1, 2, 2, 2); - - // Check diagonals - check_for_winner(game, 0, 0, 1, 1, 2, 2); - check_for_winner(game, 2, 0, 1, 1, 0, 2); - - // Check if we have a draw - if (game.game_status == IN_PROGRESS && game.cur_turn == FINAL_TURN) { - game.game_status = DRAW; - }; - } - - fun check_for_winner(game: &mut TicTacToe, row1: u64, col1: u64, row2: u64, col2: u64, row3: u64, col3: u64) { - if (game.game_status != IN_PROGRESS) { - return - }; - let result = get_winner_if_all_equal(game, row1, col1, row2, col2, row3, col3); - if (result != MARK_EMPTY) { - game.game_status = if (result == 0) X_WIN else O_WIN; - }; - } - - fun get_winner_if_all_equal(game: &TicTacToe, row1: u64, col1: u64, row2: u64, col2: u64, row3: u64, col3: u64): u8 { - let cell1 = game.get_cell(row1, col1); - let cell2 = game.get_cell(row2, col2); - let cell3 = game.get_cell(row3, col3); - if (cell1 == cell2 && cell1 == cell3) cell1 else MARK_EMPTY - } -} diff --git a/sui_programmability/examples/games/sources/tic_tac_toe.move b/sui_programmability/examples/games/sources/tic_tac_toe.move deleted file mode 100644 index 19819f1efd440..0000000000000 --- a/sui_programmability/examples/games/sources/tic_tac_toe.move +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// This is an implementation of the TicTacToe game. -// The game object (which includes gameboard) is owned by a game admin. -// Since players don't have ownership over the game object, they cannot -// mutate the gameboard directly. In order for each player to place -// a marker, they must first show their intention of placing a marker -// by creating a marker object with the placement information and send -// the marker to the admin. The admin needs to run a centralized service -// that monitors the marker placement events and respond do them. -// Upon receiving an event, the admin will attempt the place the new -// marker on the gameboard. This means that every marker placement operation -// always take two transactions, one by the player, and one by the admin. -// It also means that we need to trust the centralized service for liveness, -// i.e. the service is willing to make progress in the game. -// TicTacToeV2 shows a simpler way to implement this using shared objects, -// providing different trade-offs: using shared object is more expensive, -// however it eliminates the need of a centralized service. -module games::tic_tac_toe { - use sui::event; - - // Game status - const IN_PROGRESS: u8 = 0; - const X_WIN: u8 = 1; - const O_WIN: u8 = 2; - const DRAW: u8 = 3; - const FINAL_TURN: u8 = 8; - - // Error codes - const EInvalidLocation: u64 = 0; - const ENoMoreMark: u64 = 1; - - public struct TicTacToe has key { - id: UID, - gameboard: vector>>, - cur_turn: u8, - game_status: u8, - x_address: address, - o_address: address, - } - - public struct MarkMintCap has key { - id: UID, - game_id: ID, - remaining_supply: u8, - } - - public struct Mark has key, store { - id: UID, - player: address, - row: u64, - col: u64, - } - - public struct Trophy has key { - id: UID, - } - - public struct MarkSentEvent has copy, drop { - // The Object ID of the game object - game_id: ID, - // The object ID of the mark sent - mark_id: ID, - } - - public struct GameEndEvent has copy, drop { - // The Object ID of the game object - game_id: ID, - } - - /// `x_address` and `o_address` are the account address of the two players. - public entry fun create_game(x_address: address, o_address: address, ctx: &mut TxContext) { - // TODO: Validate sender address, only GameAdmin can create games. - - let id = object::new(ctx); - let game_id = id.to_inner(); - let gameboard = vector[ - vector[option::none(), option::none(), option::none()], - vector[option::none(), option::none(), option::none()], - vector[option::none(), option::none(), option::none()], - ]; - let game = TicTacToe { - id, - gameboard, - cur_turn: 0, - game_status: IN_PROGRESS, - x_address: x_address, - o_address: o_address, - }; - transfer::transfer(game, ctx.sender()); - let cap = MarkMintCap { - id: object::new(ctx), - game_id, - remaining_supply: 5, - }; - transfer::transfer(cap, x_address); - let cap = MarkMintCap { - id: object::new(ctx), - game_id, - remaining_supply: 5, - }; - transfer::transfer(cap, o_address); - } - - /// Generate a new mark intended for location (row, col). - /// This new mark is not yet placed, just transferred to the game. - public entry fun send_mark_to_game( - cap: &mut MarkMintCap, - game_address: address, - row: u64, - col: u64, - ctx: &mut TxContext, - ) { - if (row > 2 || col > 2) { - abort EInvalidLocation - }; - let mark = mint_mark(cap, row, col, ctx); - // Once an event is emitted, it should be observed by a game server. - // The game server will then call `place_mark` to place this mark. - event::emit(MarkSentEvent { - game_id: *&cap.game_id, - mark_id: object::id(&mark), - }); - transfer::public_transfer(mark, game_address); - } - - public entry fun place_mark(game: &mut TicTacToe, mark: Mark, ctx: &mut TxContext) { - // If we are placing the mark at the wrong turn, or if game has ended, - // destroy the mark. - let addr = game.get_cur_turn_address(); - if (game.game_status != IN_PROGRESS || &addr != &mark.player) { - mark.delete(); - return - }; - let cell = get_cell_mut_ref(game, mark.row, mark.col); - if (cell.is_some()) { - // There is already a mark in the desired location. - // Destroy the mark. - mark.delete(); - return - }; - cell.fill(mark); - game.update_winner(); - game.cur_turn = game.cur_turn + 1; - - if (game.game_status != IN_PROGRESS) { - // Notify the server that the game ended so that it can delete the game. - event::emit(GameEndEvent { game_id: object::id(game) }); - if (game.game_status == X_WIN) { - transfer::transfer(Trophy { id: object::new(ctx) }, *&game.x_address); - } else if (game.game_status == O_WIN) { - transfer::transfer(Trophy { id: object::new(ctx) }, *&game.o_address); - } - } - } - - public entry fun delete_game(game: TicTacToe) { - let TicTacToe { id, mut gameboard, cur_turn: _, game_status: _, x_address: _, o_address: _ } = game; - while (gameboard.length() > 0) { - let mut row = gameboard.pop_back(); - while (row.length() > 0) { - let mut element = row.pop_back(); - if (element.is_some()) { - let mark = element.extract(); - mark.delete(); - }; - element.destroy_none(); - }; - row.destroy_empty(); - }; - gameboard.destroy_empty(); - object::delete(id); - } - - public entry fun delete_trophy(trophy: Trophy) { - let Trophy { id } = trophy; - object::delete(id); - } - - public entry fun delete_cap(cap: MarkMintCap) { - let MarkMintCap { id, game_id: _, remaining_supply: _ } = cap; - object::delete(id); - } - - public fun get_status(game: &TicTacToe): u8 { - game.game_status - } - - fun mint_mark(cap: &mut MarkMintCap, row: u64, col: u64, ctx: &mut TxContext): Mark { - if (cap.remaining_supply == 0) { - abort ENoMoreMark - }; - cap.remaining_supply = cap.remaining_supply - 1; - Mark { - id: object::new(ctx), - player: ctx.sender(), - row, - col, - } - } - - fun get_cur_turn_address(game: &TicTacToe): address { - if (game.cur_turn % 2 == 0) { - *&game.x_address - } else { - *&game.o_address - } - } - - fun get_cell_ref(game: &TicTacToe, row: u64, col: u64): &Option { - &game.gameboard[row][col] - } - - fun get_cell_mut_ref(game: &mut TicTacToe, row: u64, col: u64): &mut Option { - &mut game.gameboard[row][col] - } - - fun update_winner(game: &mut TicTacToe) { - // Check all rows - check_for_winner(game, 0, 0, 0, 1, 0, 2); - check_for_winner(game, 1, 0, 1, 1, 1, 2); - check_for_winner(game, 2, 0, 2, 1, 2, 2); - - // Check all columns - check_for_winner(game, 0, 0, 1, 0, 2, 0); - check_for_winner(game, 0, 1, 1, 1, 2, 1); - check_for_winner(game, 0, 2, 1, 2, 2, 2); - - // Check diagonals - check_for_winner(game, 0, 0, 1, 1, 2, 2); - check_for_winner(game, 2, 0, 1, 1, 0, 2); - - // Check if we have a draw - if (game.game_status == IN_PROGRESS && game.cur_turn == FINAL_TURN) { - game.game_status = DRAW; - }; - } - - fun check_for_winner(game: &mut TicTacToe, row1: u64, col1: u64, row2: u64, col2: u64, row3: u64, col3: u64) { - if (game.game_status != IN_PROGRESS) { - return - }; - let mut result = check_all_equal(game, row1, col1, row2, col2, row3, col3); - if (result.is_some()) { - let winner = result.extract(); - game.game_status = if (&winner == &game.x_address) { - X_WIN - } else { - O_WIN - }; - }; - } - - fun check_all_equal(game: &TicTacToe, row1: u64, col1: u64, row2: u64, col2: u64, row3: u64, col3: u64): Option
{ - let cell1 = get_cell_ref(game, row1, col1); - let cell2 = get_cell_ref(game, row2, col2); - let cell3 = get_cell_ref(game, row3, col3); - if (cell1.is_some() && cell2.is_some() && cell3.is_some()) { - let cell1_player = cell1.borrow().player; - let cell2_player = cell2.borrow().player; - let cell3_player = cell3.borrow().player; - if (&cell1_player == &cell2_player && &cell1_player == &cell3_player) { - return option::some(cell1_player) - }; - }; - option::none() - } - - use fun delete_mark as Mark.delete; - - fun delete_mark(mark: Mark) { - let Mark { id, player: _, row: _, col: _ } = mark; - object::delete(id); - } - - public fun mark_player(mark: &Mark): &address { - &mark.player - } - - public fun mark_row(mark: &Mark): u64 { - mark.row - } - - public fun mark_col(mark: &Mark): u64 { - mark.col - } -} diff --git a/sui_programmability/examples/games/sources/vdf_based_lottery.move b/sui_programmability/examples/games/sources/vdf_based_lottery.move deleted file mode 100644 index 2df7aa6c0f61c..0000000000000 --- a/sui_programmability/examples/games/sources/vdf_based_lottery.move +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// A basic lottery game that depends on user-provided randomness which is processed by a verifiable delay function (VDF) -/// to make sure that it is unbiasable. -/// -/// During the submission phase, players can buy tickets. When buying a ticket, a user must provide some randomness `r`. This -/// randomness is added to the combined randomness of the lottery, `h`, as `h = Sha2_256(h, r)`. -/// -/// After the submission phase has ended, the combined randomness is used to generate an input to the VDF. Anyone may now -/// compute the output and submit it along with a proof of correctness to the `complete` function. If the output and proof -/// verifies, the game ends, and the hash of the output is used to pick a winner. -/// -/// The outcome is guaranteed to be fair if: -/// 1) At least one player contributes true randomness, -/// 2) The number of iterations is defined such that it takes at least `submission_phase_length` to compute the VDF. -module games::vdf_based_lottery { - use games::drand_lib::safe_selection; - use sui::clock::Clock; - use std::hash::sha2_256; - use sui::vdf::{hash_to_input, vdf_verify}; - - /// Error codes - const EGameNotInProgress: u64 = 0; - const EGameAlreadyCompleted: u64 = 1; - const EInvalidTicket: u64 = 2; - const ESubmissionPhaseInProgress: u64 = 3; - const EInvalidVdfProof: u64 = 4; - const ESubmissionPhaseFinished: u64 = 5; - const EInvalidRandomness: u64 = 6; - - /// Game status - const IN_PROGRESS: u8 = 0; - const COMPLETED: u8 = 1; - - /// Other constants - const RANDOMNESS_LENGTH: u64 = 16; - - /// Game represents a set of parameters of a single game. - /// This game can be extended to require ticket purchase, reward winners, etc. - /// - public struct Game has key, store { - id: UID, - iterations: u64, - status: u8, - timestamp_start: u64, - submission_phase_length: u64, - participants: u64, - vdf_input_seed: vector, - winner: Option, - } - - /// Ticket represents a participant in a single game. - /// Can be deconstructed only by the owner. - public struct Ticket has key, store { - id: UID, - game_id: ID, - participant_index: u64, - } - - /// GameWinner represents a participant that won in a specific game. - /// Can be deconstructed only by the owner. - public struct GameWinner has key, store { - id: UID, - game_id: ID, - } - - /// Create a shared-object Game. - public fun create(iterations: u64, submission_phase_length: u64, clock: &Clock, ctx: &mut TxContext) { - let game = Game { - id: object::new(ctx), - iterations, - status: IN_PROGRESS, - timestamp_start: clock.timestamp_ms(), - submission_phase_length, - vdf_input_seed: vector::empty(), - participants: 0, - winner: option::none(), - }; - transfer::public_share_object(game); - } - - /// Anyone can participate in the game and receive a ticket. - public fun participate(self: &mut Game, my_randomness: vector, clock: &Clock, ctx: &mut TxContext): Ticket { - assert!(self.status == IN_PROGRESS, EGameNotInProgress); - assert!(clock.timestamp_ms() - self.timestamp_start < self.submission_phase_length, ESubmissionPhaseFinished); - - // Update combined randomness by concatenating the provided randomness and hashing it - assert!(my_randomness.length() == RANDOMNESS_LENGTH, EInvalidRandomness); - self.vdf_input_seed.append(my_randomness); - self.vdf_input_seed = sha2_256(self.vdf_input_seed); - - // Assign index to this participant - let participant_index = self.participants; - self.participants = self.participants + 1; - - Ticket { - id: object::new(ctx), - game_id: object::id(self), - participant_index, - } - } - - /// Complete this lottery by sending VDF output and proof for the seed created from the - /// contributed randomness. Anyone can call this. - public fun complete(self: &mut Game, vdf_output: vector, vdf_proof: vector, clock: &Clock) { - assert!(self.status != COMPLETED, EGameAlreadyCompleted); - assert!(clock.timestamp_ms() - self.timestamp_start >= self.submission_phase_length, ESubmissionPhaseInProgress); - - // Hash combined randomness to vdf input - let vdf_input = hash_to_input(&self.vdf_input_seed); - - // Verify output and proof - assert!(vdf_verify(&vdf_input, &vdf_output, &vdf_proof, self.iterations), EInvalidVdfProof); - - // The randomness is derived from the VDF output by passing it through a hash function with uniformly distributed - // output to make it uniform. Any hash function with uniformly distributed output can be used. - let randomness = sha2_256(vdf_output); - - // Set winner and mark lottery completed - self.winner = option::some(safe_selection(self.participants, &randomness)); - self.status = COMPLETED; - } - - /// The winner can redeem its ticket. - public fun redeem(self: &Game, ticket: &Ticket, ctx: &mut TxContext): GameWinner { - assert!(self.status == COMPLETED, ESubmissionPhaseInProgress); - assert!(object::id(self) == ticket.game_id, EInvalidTicket); - assert!(self.winner.contains(&ticket.participant_index), EInvalidTicket); - - GameWinner { - id: object::new(ctx), - game_id: ticket.game_id, - } - } - - // Note that a ticket can be deleted before the game was completed. - public use fun delete_ticket as Ticket.delete; - public fun delete_ticket(ticket: Ticket) { - let Ticket { id, game_id: _, participant_index: _} = ticket; - object::delete(id); - } - - public use fun delete_game_winner as GameWinner.delete; - public fun delete_game_winner(ticket: GameWinner) { - let GameWinner { id, game_id: _} = ticket; - object::delete(id); - } - - public use fun ticket_game_id as Ticket.game_id; - public fun ticket_game_id(ticket: &Ticket): &ID { - &ticket.game_id - } - - public use fun game_winner_game_id as GameWinner.game_id; - public fun game_winner_game_id(ticket: &GameWinner): &ID { - &ticket.game_id - } - -} diff --git a/sui_programmability/examples/games/tests/drand_based_lottery_tests.move b/sui_programmability/examples/games/tests/drand_based_lottery_tests.move deleted file mode 100644 index 5ad5f2a7ad0c1..0000000000000 --- a/sui_programmability/examples/games/tests/drand_based_lottery_tests.move +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::drand_based_lottery_tests { - use sui::test_scenario::{Self}; - use games::drand_based_lottery::{Self, Game, Ticket, GameWinner}; - use games::drand_lib::verify_time_has_passed; - - #[test] - fun test_verify_time_has_passed_success() { - // Taken from the output of - // curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/8 - verify_time_has_passed( - 1692803367 + 3*7, // exactly the 8th round - x"a0c06b9964123d2e6036aa004c140fc301f4edd3ea6b8396a15dfd7dfd70cc0dce0b4a97245995767ab72cf59de58c47", - 8 - ); - verify_time_has_passed( - 1692803367 + 3*7 - 2, // the 8th round - 2 seconds - x"a0c06b9964123d2e6036aa004c140fc301f4edd3ea6b8396a15dfd7dfd70cc0dce0b4a97245995767ab72cf59de58c47", - 8 - ); - } - - #[test] - #[expected_failure(abort_code = games::drand_lib::EInvalidProof)] - fun test_verify_time_has_passed_failure() { - // Taken from the output of - // curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/8 - verify_time_has_passed( - 1692803367 + 3*8, // exactly the 9th round - 10 seconds - x"a0c06b9964123d2e6036aa004c140fc301f4edd3ea6b8396a15dfd7dfd70cc0dce0b4a97245995767ab72cf59de58c47", - 8 - ); - } - - #[test] - fun test_play_drand_lottery() { - let user1 = @0x0; - let user2 = @0x1; - let user3 = @0x2; - let user4 = @0x3; - - let mut scenario = test_scenario::begin(user1); - - drand_based_lottery::create(10, scenario.ctx()); - scenario.next_tx(user1); - let mut game_val = scenario.take_shared(); - let game = &mut game_val; - - // User1 buys a ticket. - scenario.next_tx(user1); - game.participate(scenario.ctx()); - // User2 buys a ticket. - scenario.next_tx(user2); - game.participate(scenario.ctx()); - // User3 buys a tcket - scenario.next_tx(user3); - game.participate(scenario.ctx()); - // User4 buys a tcket - scenario.next_tx(user4); - game.participate(scenario.ctx()); - - // User 2 closes the game. - scenario.next_tx(user2); - // Taken from the output of - // curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/8 - game.close( - x"a0c06b9964123d2e6036aa004c140fc301f4edd3ea6b8396a15dfd7dfd70cc0dce0b4a97245995767ab72cf59de58c47", - ); - - // User3 completes the game. - scenario.next_tx(user3); - // Taken from theoutput of - // curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/10 - game.complete( - x"ac415e508c484053efed1c6c330e3ae0bf20185b66ed088864dac1ff7d6f927610824986390d3239dac4dd73e6f865f5", - ); - - // User2 is the winner since the mod of the hash results in 1. - scenario.next_tx(user2); - assert!(!test_scenario::has_most_recent_for_address(user2), 1); - let ticket = scenario.take_from_address(user2); - let ticket_game_id = *ticket.get_ticket_game_id(); - ticket.redeem(&game_val, scenario.ctx()); - ticket.delete_ticket(); - - // Make sure User2 now has a winner ticket for the right game id. - scenario.next_tx(user2); - let ticket = scenario.take_from_address(user2); - assert!(ticket.get_game_winner_game_id() == &ticket_game_id, 1); - test_scenario::return_to_address(user2, ticket); - - test_scenario::return_shared(game_val); - scenario.end(); - } -} diff --git a/sui_programmability/examples/games/tests/drand_based_scratch_card_tests.move b/sui_programmability/examples/games/tests/drand_based_scratch_card_tests.move deleted file mode 100644 index 4f12459150f10..0000000000000 --- a/sui_programmability/examples/games/tests/drand_based_scratch_card_tests.move +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::drand_based_scratch_card_tests { - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::test_scenario::{Self, Scenario}; - - use games::drand_based_scratch_card; - - fun mint(addr: address, amount: u64, scenario: &mut Scenario) { - transfer::public_transfer(coin::mint_for_testing(amount, scenario.ctx()), addr); - scenario.next_tx(addr); - } - - #[test] - fun test_play_drand_scratch_card_with_winner() { - let user1 = @0x0; - let user2 = @0x1; - - let mut scenario = test_scenario::begin(user1); - - // Create the game and get back the output objects. - mint(user1, 10, &mut scenario); - let coin1 = scenario.take_from_sender>(); - drand_based_scratch_card::create(coin1, 10, 10, scenario.ctx()); - scenario.next_tx(user1); - let game = scenario.take_immutable(); - let mut reward_val = scenario.take_shared(); - let drand_final_round = drand_based_scratch_card::end_of_game_round(game.get_game_base_drand_round()); - assert!(drand_final_round == 58810, 1); - - // Since everything here is deterministic, we know that the 49th ticket will be a winner. - let mut i = 0; - loop { - // User2 buys a ticket. - scenario.next_tx(user2); - mint(user2, 1, &mut scenario); - let coin2 = scenario.take_from_sender>(); - drand_based_scratch_card::buy_ticket(coin2, &game, scenario.ctx()); - scenario.next_tx(user1); - let coin1 = scenario.take_from_sender>(); - assert!(coin1.value() == 1, 1); - scenario.return_to_sender(coin1); - scenario.next_tx(user2); - let ticket = scenario.take_from_sender(); - // Generated using: - // curl https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/58810 - ticket.evaluate( - &game, - x"876b8586ed9522abd0ca596d6e214e9a7e9bedc4a2e9698d27970e892287268062aba93fd1a7c24fcc188a4c7f0a0e98", - scenario.ctx() - ); - scenario.next_tx(user2); - if (scenario.has_most_recent_for_sender()) { - break - }; - i = i + 1; - }; - // This value may change if the object ID is changed. - assert!(i == 3, 1); - - // Claim the reward. - let winner = scenario.take_from_sender(); - scenario.next_tx(user2); - let reward = &mut reward_val; - winner.take_reward(reward, scenario.ctx()); - scenario.next_tx(user2); - let coin2 = scenario.take_from_sender>(); - assert!(coin2.value() == 10, 1); - scenario.return_to_sender(coin2); - - test_scenario::return_shared(reward_val); - test_scenario::return_immutable(game); - scenario.end(); - } - - #[test] - fun test_play_drand_scratch_card_without_winner() { - let user1 = @0x0; - - let mut scenario = test_scenario::begin(user1); - - // Create the game and get back the output objects. - mint(user1, 10, &mut scenario); - let coin1 = scenario.take_from_sender>(); - drand_based_scratch_card::create(coin1, 10, 10, scenario.ctx()); - scenario.next_tx(user1); - let game = scenario.take_immutable(); - let mut reward = scenario.take_shared(); - - // More 4 epochs forward. - scenario.next_epoch(user1); - scenario.next_epoch(user1); - scenario.next_epoch(user1); - scenario.next_epoch(user1); - - // Take back the reward. - reward.redeem(&game, scenario.ctx()); - scenario.next_tx(user1); - let coin1 = scenario.take_from_sender>(); - assert!(coin1.value() == 10, 1); - scenario.return_to_sender(coin1); - - test_scenario::return_shared(reward); - test_scenario::return_immutable(game); - scenario.end(); - } -} diff --git a/sui_programmability/examples/games/tests/rock_paper_scissors_tests.move b/sui_programmability/examples/games/tests/rock_paper_scissors_tests.move deleted file mode 100644 index eed46feea6f3b..0000000000000 --- a/sui_programmability/examples/games/tests/rock_paper_scissors_tests.move +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::rock_paper_scissors_tests { - use games::rock_paper_scissors::{Self as game, Game, PlayerTurn, Secret, ThePrize}; - use sui::test_scenario::{Self}; - use std::hash; - - #[test] - fun play_rock_paper_scissors() { - // So these are our heroes. - let the_main_guy = @0xA1C05; - let mr_lizard = @0xA55555; - let mr_spock = @0x590C; - - let mut scenario = test_scenario::begin(the_main_guy); - - // Let the game begin! - game::new_game(mr_spock, mr_lizard, scenario.ctx()); - - // Mr Spock makes his move. He does it secretly and hashes the gesture with a salt - // so that only he knows what it is. - scenario.next_tx(mr_spock); - { - let hash = hash(game::rock(), b"my_phaser_never_failed_me!"); - game::player_turn(the_main_guy, hash, scenario.ctx()); - }; - - // Now it's time for The Main Guy to accept his turn. - scenario.next_tx(the_main_guy); - { - let mut game = scenario.take_from_sender(); - let cap = scenario.take_from_sender(); - - assert!(game.status() == 0, 0); // STATUS_READY - - game.add_hash(cap); - - assert!(game.status() == 1, 0); // STATUS_HASH_SUBMISSION - - scenario.return_to_sender(game); - }; - - // Same for Mr Lizard. He uses his secret phrase to encode his turn. - scenario.next_tx(mr_lizard); - { - let hash = hash(game::scissors(), b"sssssss_you_are_dead!"); - game::player_turn(the_main_guy, hash, scenario.ctx()); - }; - - scenario.next_tx(the_main_guy); - { - let mut game = scenario.take_from_sender(); - let cap = scenario.take_from_sender(); - game.add_hash(cap); - - assert!(game.status() == 2, 0); // STATUS_HASHES_SUBMITTED - - scenario.return_to_sender(game); - }; - - // Now that both sides made their moves, it's time for Mr Spock and Mr Lizard to - // reveal their secrets. The Main Guy will then be able to determine the winner. Who's - // gonna win The Prize? We'll see in a bit! - scenario.next_tx(mr_spock); - game::reveal(the_main_guy, b"my_phaser_never_failed_me!", scenario.ctx()); - - scenario.next_tx(the_main_guy); - { - let mut game = scenario.take_from_sender(); - let secret = scenario.take_from_sender(); - game.match_secret(secret); - - assert!(game.status() == 3, 0); // STATUS_REVEALING - - scenario.return_to_sender(game); - }; - - scenario.next_tx(mr_lizard); - game::reveal(the_main_guy, b"sssssss_you_are_dead!", scenario.ctx()); - - // The final step. The Main Guy matches and reveals the secret of the Mr Lizard and - // calls the [`select_winner`] function to release The Prize. - scenario.next_tx(the_main_guy); - { - let mut game = scenario.take_from_sender(); - let secret = scenario.take_from_sender(); - game.match_secret(secret); - - assert!(game.status() == 4, 0); // STATUS_REVEALED - - game.select_winner(scenario.ctx()); - }; - - scenario.next_tx(mr_spock); - // If it works, then MrSpock is in possession of the prize; - let prize = scenario.take_from_sender(); - // Don't forget to give it back! - scenario.return_to_sender(prize); - scenario.end(); - } - - // Copy of the hashing function from the main module. - fun hash(gesture: u8, mut salt: vector): vector { - salt.push_back(gesture); - hash::sha2_256(salt) - } -} diff --git a/sui_programmability/examples/games/tests/shared_tic_tac_toe_tests.move b/sui_programmability/examples/games/tests/shared_tic_tac_toe_tests.move deleted file mode 100644 index 0cc0fc8e322d4..0000000000000 --- a/sui_programmability/examples/games/tests/shared_tic_tac_toe_tests.move +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::shared_tic_tac_toe_tests { - use sui::test_scenario::{Self, Scenario}; - use games::shared_tic_tac_toe::{Self, TicTacToe, Trophy}; - - const IN_PROGRESS: u8 = 0; - const X_WIN: u8 = 1; - const DRAW: u8 = 3; - - #[test] - fun play_tictactoe() { - let player_x = @0x0; - let player_o = @0x1; - - // Anyone can create a game, because the game object will be eventually shared. - let mut scenario_val = test_scenario::begin(player_x); - let scenario = &mut scenario_val; - shared_tic_tac_toe::create_game(copy player_x, copy player_o, scenario.ctx()); - // Player1 places an X in (1, 1). - place_mark(1, 1, player_x, scenario); - /* - Current game board: - _|_|_ - _|X|_ - | | - */ - - // Player2 places an O in (0, 0). - place_mark(0, 0, player_o, scenario); - /* - Current game board: - O|_|_ - _|X|_ - | | - */ - - // Player1 places an X in (0, 2). - place_mark(0, 2, player_x, scenario); - /* - Current game board: - O|_|X - _|X|_ - | | - */ - - // Player2 places an O in (1, 0). - let mut status = place_mark(1, 0, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|_|X - O|X|_ - | | - */ - - // Opportunity for Player1! Player1 places an X in (2, 0). - status = place_mark(2, 0, player_x, scenario); - /* - Current game board: - O|_|X - O|X|_ - X| | - */ - - // Check that X has won! - assert!(status == X_WIN, 2); - - // X has the Trophy - scenario.next_tx(player_x); - assert!( - scenario.has_most_recent_for_sender(), - 1 - ); - - scenario.next_tx(player_o); - // O has no Trophy - assert!( - !scenario.has_most_recent_for_sender(), - 2 - ); - scenario_val.end(); - } - - - #[test] - fun play_tictactoe_draw() { - let player_x = @0x0; - let player_o = @0x1; - - // Anyone can create a game, because the game object will be eventually shared. - let mut scenario_val = test_scenario::begin(player_x); - let scenario = &mut scenario_val; - shared_tic_tac_toe::create_game(copy player_x, copy player_o, scenario.ctx()); - // Player1 places an X in (0, 1). - let mut status = place_mark(0, 1, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - _|X|_ - _|_|_ - | | - */ - - // Player2 places an O in (0, 0). - status = place_mark(0, 0, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|_|_ - | | - */ - - // Player1 places an X in (1, 1). - status = place_mark(1, 1, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - | | - */ - - // Player2 places an O in (2, 1). - status = place_mark(2, 1, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - |O| - */ - - // Player1 places an X in (2, 0). - status = place_mark(2, 0, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - X|O| - */ - - // Player2 places an O in (0, 2). - status = place_mark(0, 2, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - _|X|_ - X|O| - */ - - // Player1 places an X in (1, 2). - status = place_mark(1, 2, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - _|X|X - X|O| - */ - - // Player2 places an O in (1, 0). - status = place_mark(1, 0, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - O|X|X - X|O| - */ - - // Player1 places an X in (2, 2). - status = place_mark(2, 2, player_x, scenario); - /* - Current game board: - O|X|O - O|X|X - X|O|X - */ - - // We have a draw. - assert!(status == DRAW, 2); - - // No one has the trophy - scenario.next_tx(player_x); - assert!( - !scenario.has_most_recent_for_sender(), - 1 - ); - scenario.next_tx(player_o); - assert!( - !scenario.has_most_recent_for_sender(), - 1 - ); - scenario_val.end(); - } - - - fun place_mark( - row: u8, - col: u8, - player: address, - scenario: &mut Scenario, - ): u8 { - // The gameboard is now a shared object. - // Any player can place a mark on it directly. - scenario.next_tx(player); - let status; - { - let mut game_val = scenario.take_shared(); - let game = &mut game_val; - game.place_mark(row, col, scenario.ctx()); - status = game.get_status(); - test_scenario::return_shared(game_val); - }; - status - } -} diff --git a/sui_programmability/examples/games/tests/tic_tac_toe_tests.move b/sui_programmability/examples/games/tests/tic_tac_toe_tests.move deleted file mode 100644 index cb568cb7aabaa..0000000000000 --- a/sui_programmability/examples/games/tests/tic_tac_toe_tests.move +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::tic_tac_toe_tests { - use sui::test_scenario::{Self, Scenario}; - use games::tic_tac_toe::{Self, Mark, MarkMintCap, TicTacToe, Trophy}; - - const IN_PROGRESS: u8 = 0; - const X_WIN: u8 = 1; - const DRAW: u8 = 3; - - #[test] - fun play_tictactoe() { - let admin = @0x0; - let player_x = @0x1; - let player_o = @0x2; - - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - // Admin creates a game - tic_tac_toe::create_game(player_x, player_o, scenario.ctx()); - // Player1 places an X in (1, 1). - place_mark(1, 1, admin, player_x, scenario); - /* - Current game board: - _|_|_ - _|X|_ - | | - */ - - // Player2 places an O in (0, 0). - place_mark(0, 0, admin, player_o, scenario); - /* - Current game board: - O|_|_ - _|X|_ - | | - */ - - // Player1 places an X in (0, 2). - place_mark(0, 2, admin, player_x, scenario); - /* - Current game board: - O|_|X - _|X|_ - | | - */ - - // Player2 places an O in (1, 0). - let mut status = place_mark(1, 0, admin, player_o, scenario); - /* - Current game board: - O|_|X - O|X|_ - | | - */ - - // Opportunity for Player1! Player1 places an X in (2, 0). - assert!(status == IN_PROGRESS, 1); - status = place_mark(2, 0, admin, player_x, scenario); - - /* - Current game board: - O|_|X - O|X|_ - X| | - */ - - // Check that X has won! - assert!(status == X_WIN, 2); - - // X has the trophy - scenario.next_tx(player_x); - assert!( - scenario.has_most_recent_for_sender(), - 1 - ); - - scenario.next_tx(player_o); - // O has no Trophy - assert!( - !scenario.has_most_recent_for_sender(), - 1 - ); - scenario_val.end(); - } - - - #[test] - fun play_tictactoe_draw() { - let admin = @0x0; - let player_x = @0x1; - let player_o = @0x2; - - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - - tic_tac_toe::create_game(player_x, player_o, scenario.ctx()); - // Player1 places an X in (0, 1). - let mut status = place_mark(0, 1, admin, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - _|X|_ - _|_|_ - | | - */ - - // Player2 places an O in (0, 0). - status = place_mark(0, 0, admin, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|_|_ - | | - */ - - // Player1 places an X in (1, 1). - status = place_mark(1, 1, admin, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - | | - */ - - // Player2 places an O in (2, 1). - status = place_mark(2, 1, admin, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - |O| - */ - - // Player1 places an X in (2, 0). - status = place_mark(2, 0, admin, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|_ - _|X|_ - X|O| - */ - - // Player2 places an O in (0, 2). - status = place_mark(0, 2, admin, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - _|X|_ - X|O| - */ - - // Player1 places an X in (1, 2). - status = place_mark(1, 2, admin, player_x, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - _|X|X - X|O| - */ - - // Player2 places an O in (1, 0). - status = place_mark(1, 0, admin, player_o, scenario); - assert!(status == IN_PROGRESS, 1); - /* - Current game board: - O|X|O - O|X|X - X|O| - */ - - // Player1 places an X in (2, 2). - status = place_mark(2, 2, admin, player_x, scenario); - /* - Current game board: - O|X|O - O|X|X - X|O|X - */ - - // We have a draw. - assert!(status == DRAW, 2); - - // No one has the trophy - scenario.next_tx(player_x); - assert!( - !scenario.has_most_recent_for_sender(), - 1 - ); - scenario.next_tx(player_o); - assert!( - !scenario.has_most_recent_for_sender(), - 1 - ); - scenario_val.end(); - } - - fun place_mark( - row: u64, - col: u64, - admin: address, - player: address, - scenario: &mut Scenario, - ): u8 { - // Step 1: player creates a mark and sends it to the game. - scenario.next_tx(player); - { - let mut cap = scenario.take_from_sender(); - cap.send_mark_to_game(admin, row, col, scenario.ctx()); - scenario.return_to_sender(cap); - }; - // Step 2: Admin places the received mark on the game board. - scenario.next_tx(admin); - let status; - { - let mut game = scenario.take_from_sender(); - let mark = scenario.take_from_sender(); - assert!(mark.mark_player() == &player, 0); - assert!(mark.mark_row() == row, 1); - assert!(mark.mark_col() == col, 2); - game.place_mark(mark, scenario.ctx()); - status = game.get_status(); - scenario.return_to_sender(game); - }; - // return the game status - status - } -} diff --git a/sui_programmability/examples/games/tests/vdf_based_lottery_tests.move b/sui_programmability/examples/games/tests/vdf_based_lottery_tests.move deleted file mode 100644 index 61631f420d55f..0000000000000 --- a/sui_programmability/examples/games/tests/vdf_based_lottery_tests.move +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::vdf_based_lottery_tests { - use sui::test_scenario as ts; - use sui::clock; - use games::vdf_based_lottery::{Self, Game, GameWinner}; - - #[test] - #[expected_failure(abort_code = games::vdf_based_lottery::ESubmissionPhaseInProgress)] - fun test_complete_too_early() { - let user1 = @0x0; - - let mut ts = ts::begin(user1); - let mut clock = clock::create_for_testing(ts.ctx()); - - vdf_based_lottery::create(1000, 1000, &clock, ts.ctx()); - ts.next_tx(user1); - let mut game = ts.take_shared(); - - // User1 buys a ticket. - ts.next_tx(user1); - let t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); - - // Increment time but still in submission phase - clock.increment_for_testing(500); - - // User1 tries to complete the lottery too early. - ts.next_tx(user1); - game.complete( - x"c0014d00b5e624fe10d1cc1e593c0ffb8c3084e49bb70efc4337640a73990bb29dfb430b55710475bcc7524c77627d8067415fffa63e0e84b1204225520fea384999719c66dbdc6e91863d99c64674af971631b56e22b7cc780765bf12d53edea1dadf566f80a62769e287a1e195596d4894b2e1360e451cbf06864762275b4d5063871d45627dea2e42ab93d5345bf172b9724216627abbf295b35a8e64e13e585bca54848a90212c9f7a3adffc25c3b87eefa7d4ab1660b523bf6410b9a9ea0e00c001327d73bebc768d150beb2a1b0de9e80c69ed594ae7787d548af44eb1e0a03616100133146c9c1202ea3a35c331864f3bfe59ffa3c88d6acb8af7a4b1b5ea842c4c4c88d415539389e614876d738d80217a3ad16d001f2de60f62b04a9d8de7ccb4716c3368f0d42e3e719dbb07bdb4355f0e5569714fbcc130f30ac7b49a5b207a444c7e00a0c27edae10c28b05f87545f337283f90c4e4ed69639683154d6a89e6589db4d18702d3a29705b434dc32e10fcbd3c62d1da20b45dba511bcecdf7c101009db7911c39f3937785dd8bd0e9db56c94777bdd867897493f82e0931e0e5facb730c3aa400f815f3d2f61de94373209dcbf9210c5e1f179b675adce2be7159238cb5f89c568004ccfc75d1b3de99a060255bcd6c9dd9a674844251ec27c48119b870f4ce63cac2081541c4269bbfa60148f9dbf2c60c76099a6538a33e88c24ce092e6aad9bdfff330470ffb87b01b666dd98e44843b41896f2be688361fe062692b441b4dd8f8ecfa96948d13daf486259bb8934cab7e9d9788da0eac7edf56", - x"c0010f1ea16b16f3fc46eb0b00a516092a6ab389e259a3013eee5e39e130702de85954b8aac435e724ad0bfd210ab7789fb91b54ac4352154f3251e0a87ccfd2c9a57d26468a384f527e129fc82b33c04b3ebbec3a99967798a95b39820c35ea015fdf4c81e143004b34b99e63462cf350689b2abdd6c3903adcbe55781d3a89c89dc571c312f9a80911a9d64884747319574b3a4ded25478e6d64b9cfb25d9c67366bc25d9ac99bcdba16665158da50a2ba179893292c4b7e76502ecaba1337d693c001fb3867669e0d4e45aa43d959dbe33c3d35b00e8414d1cf1bb9552726bb95bafa0a2c12a014a3b8fb0bd5ab9a40430ff59364b19d58d80665fee0bfee272a38c45413a3688832bf9bcacf7b5723436c120878f85ce084e72b13246ecfec7cd6a5d79e13296bbb51af785c10afe6c4f07f43a5bc711dc398271185d700b1695310d8e428ad3bc6b81a4faac2f5009b723460dbd260c940dfac06e34854d204dc779f94ab3f67847a7b90855dadc3962871c022e172e96b39a08648e045e14dad87c10102f976797f14be801a441f19771a4835640a74cf7c6ad216f18d9cdaf461bb56a897b804e053cd6cc68d659bd9f0ed985f094932d306c1bd76450bd349db3a81008d7591bc826a36583c3c361add7a8f245d18007d79704d79ae27eb08b52a44af17e2f23b441919049f061d69bac3a09c3e15074e4d75cf82f42dbff1c62ddc94fe6167ccb7265e7eab0def7d30d97be441ad763705dd30d4040815996e34643bf6d7a4f06c22aa5d6d5dd30253ea8aa59607724bb71f5425a5e7fee03b7e9fe8", - &clock - ); - - t1.delete(); - - sui::clock::destroy_for_testing(clock); - ts::return_shared(game); - ts.end(); - } - - #[test] - fun test_play_vdf_lottery() { - let user1 = @0x0; - let user2 = @0x1; - let user3 = @0x2; - let user4 = @0x3; - - let mut ts = ts::begin(user1); - let mut clock = clock::create_for_testing(ts.ctx()); - - vdf_based_lottery::create(1000, 1000, &clock, ts.ctx()); - ts.next_tx(user1); - let mut game = ts.take_shared(); - - // User1 buys a ticket. - ts.next_tx(user1); - let t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); - // User2 buys a ticket. - ts.next_tx(user2); - let t2 = game.participate(b"user2 randomness", &clock, ts.ctx()); - // User3 buys a ticket - ts.next_tx(user3); - let t3 = game.participate(b"user3 randomness", &clock, ts.ctx()); - // User4 buys a ticket - ts.next_tx(user4); - let t4 = game.participate(b"user4 randomness", &clock, ts.ctx()); - - // Increment time to after submission phase has ended - clock.increment_for_testing(1000); - - // User3 completes by submitting output and proof of the VDF - ts.next_tx(user3); - game.complete( - x"c0014d00b5e624fe10d1cc1e593c0ffb8c3084e49bb70efc4337640a73990bb29dfb430b55710475bcc7524c77627d8067415fffa63e0e84b1204225520fea384999719c66dbdc6e91863d99c64674af971631b56e22b7cc780765bf12d53edea1dadf566f80a62769e287a1e195596d4894b2e1360e451cbf06864762275b4d5063871d45627dea2e42ab93d5345bf172b9724216627abbf295b35a8e64e13e585bca54848a90212c9f7a3adffc25c3b87eefa7d4ab1660b523bf6410b9a9ea0e00c001327d73bebc768d150beb2a1b0de9e80c69ed594ae7787d548af44eb1e0a03616100133146c9c1202ea3a35c331864f3bfe59ffa3c88d6acb8af7a4b1b5ea842c4c4c88d415539389e614876d738d80217a3ad16d001f2de60f62b04a9d8de7ccb4716c3368f0d42e3e719dbb07bdb4355f0e5569714fbcc130f30ac7b49a5b207a444c7e00a0c27edae10c28b05f87545f337283f90c4e4ed69639683154d6a89e6589db4d18702d3a29705b434dc32e10fcbd3c62d1da20b45dba511bcecdf7c101009db7911c39f3937785dd8bd0e9db56c94777bdd867897493f82e0931e0e5facb730c3aa400f815f3d2f61de94373209dcbf9210c5e1f179b675adce2be7159238cb5f89c568004ccfc75d1b3de99a060255bcd6c9dd9a674844251ec27c48119b870f4ce63cac2081541c4269bbfa60148f9dbf2c60c76099a6538a33e88c24ce092e6aad9bdfff330470ffb87b01b666dd98e44843b41896f2be688361fe062692b441b4dd8f8ecfa96948d13daf486259bb8934cab7e9d9788da0eac7edf56", - x"c0010f1ea16b16f3fc46eb0b00a516092a6ab389e259a3013eee5e39e130702de85954b8aac435e724ad0bfd210ab7789fb91b54ac4352154f3251e0a87ccfd2c9a57d26468a384f527e129fc82b33c04b3ebbec3a99967798a95b39820c35ea015fdf4c81e143004b34b99e63462cf350689b2abdd6c3903adcbe55781d3a89c89dc571c312f9a80911a9d64884747319574b3a4ded25478e6d64b9cfb25d9c67366bc25d9ac99bcdba16665158da50a2ba179893292c4b7e76502ecaba1337d693c001fb3867669e0d4e45aa43d959dbe33c3d35b00e8414d1cf1bb9552726bb95bafa0a2c12a014a3b8fb0bd5ab9a40430ff59364b19d58d80665fee0bfee272a38c45413a3688832bf9bcacf7b5723436c120878f85ce084e72b13246ecfec7cd6a5d79e13296bbb51af785c10afe6c4f07f43a5bc711dc398271185d700b1695310d8e428ad3bc6b81a4faac2f5009b723460dbd260c940dfac06e34854d204dc779f94ab3f67847a7b90855dadc3962871c022e172e96b39a08648e045e14dad87c10102f976797f14be801a441f19771a4835640a74cf7c6ad216f18d9cdaf461bb56a897b804e053cd6cc68d659bd9f0ed985f094932d306c1bd76450bd349db3a81008d7591bc826a36583c3c361add7a8f245d18007d79704d79ae27eb08b52a44af17e2f23b441919049f061d69bac3a09c3e15074e4d75cf82f42dbff1c62ddc94fe6167ccb7265e7eab0def7d30d97be441ad763705dd30d4040815996e34643bf6d7a4f06c22aa5d6d5dd30253ea8aa59607724bb71f5425a5e7fee03b7e9fe8", - &clock - ); - - // User1 is the winner since the mod of the hash results in 0. - ts.next_tx(user1); - assert!(!ts::has_most_recent_for_address(user1), 1); - let winner = game.redeem(&t1, ts.ctx()); - - // Make sure User1 now has a winner ticket for the right game id. - ts.next_tx(user1); - assert!(winner.game_id() == t1.game_id(), 1); - - t1.delete(); - t2.delete(); - t3.delete(); - t4.delete(); - winner.delete(); - - clock.destroy_for_testing(); - ts::return_shared(game); - ts.end(); - } - - #[test] - #[expected_failure(abort_code = games::vdf_based_lottery::EInvalidVdfProof)] - fun test_invalid_vdf_output() { - let user1 = @0x0; - let user2 = @0x1; - let user3 = @0x2; - let user4 = @0x3; - - let mut ts = ts::begin(user1); - let mut clock = clock::create_for_testing(ts.ctx()); - - vdf_based_lottery::create(1000, 1000, &clock, ts.ctx()); - ts.next_tx(user1); - let mut game = ts.take_shared(); - - // User1 buys a ticket. - ts.next_tx(user1); - let t1 = game.participate(b"user1 randomness", &clock, ts.ctx()); - // User2 buys a ticket. - ts.next_tx(user2); - let t2 = game.participate(b"user2 randomness", &clock, ts.ctx()); - // User3 buys a ticket - ts.next_tx(user3); - let t3 = game.participate(b"user3 randomness", &clock, ts.ctx()); - // User4 buys a ticket - ts.next_tx(user4); - let t4 = game.participate(b"user4 randomness", &clock, ts.ctx()); - - // Increment time to after submission phase has ended - clock.increment_for_testing(1000); - - // User3 completes by submitting output and proof of the VDF - ts.next_tx(user3); - game.complete( - x"c001503be6eff558a40ba145da5aa9d1270367f32cde44c1601846cdd3d0911abce8ab6adb6b82682b45107545e9ae1efca44dd6f3ba8d95a687652b94f479f6b1a37156c44194d6bc5f266098b75251b12de6fa67de6aea14250c0481694db5f24db5e3c89da3354aafc10c4bd0371f9a175d1b5b193190c4c1089ed7a95dc07f1ce29021b55f3aaa7eb65725d61277f0996b783c005a919ba121d81f211f63d188ac525056235504fe4858765dc6498362d98e8540287a0ff78424c18de53abe46c0014f847bd49f599960fe3c3b7cfc571cd854c7d21b0e9984070f7e168c872a6e6480d8fd37d30602f57a237b83ae961e6a4acb94b78c32d04f06058bda037d6ad313c81f823db25c53c265b02a29008f727f95010c82b0cf8745e77a7f4000dac929ba83a4594482b4e6ff59c93a78df5c816f244914329c145e288fd3fd4800a1cc2df23f386112e569608e6de40ee65fe870960b4e3fee4bb188d8db0dd5df3c2384eb24a797eb20cf8524d563663ccde866a405e2713cfafdb760e50c77a797c10100a31fc5ca0a91aa788d5f5df17a1433f1a0e6e4da440ce935b1b48dc6868c8fc00d7ee725ce21797a6c4440af02570466081479e99eee1a5b509a3e1ac2e000ed386c35d9fadd130df2a292fa5f9aa2c195c48c9d11e58ac98c8dbd2169721ed2d2c9f5544de17deeaa9655360ed7baa46820f5e008af1e3f028d819dee3fee50ab55b266385dfc8f65f7f0c1b6149e5295bfefb83b14db3a30b2cefd1495ba4e5ae39d2b729f9644fc28764d03243fad3e61145ed83cbf2708b60c0b7cac7148", - x"0101010180032cf35709e1301d02b40a0dbe3dadfe6ec1eeba8fb8060a1decd0c7a126ea3f27fadcad81435601b0e0abca5c89173ef639e5a88043aa29801e6799e430b509e479b57af981f9ddd48d3a8d5919f99258081557a08270bb441233c78030a01e03ec199b5e3eef5ccc9b1a3d4841cbe4ff529c22a8cd1b1b0075338d864e3890942df6b007d2c3e3a8ef1ce7490c6bbec5372adfcbf8704a1ffc9a69db8d9cdc54762f019036e450e457325eef74b794f3f16ff327d68079a5b9de49163d7323937374f8a785a8f9afe84d6a71b336e4de00f239ee3af1d7604a3985e610e1603bd0e1a4998e19fa0c8920ffd8d61b0a87eeee50ac7c03ff7c4708a34f3bc92fd0103758c954ee34032cee2c78ad8cdc79a35dbc810196b7bf6833e1c45c83b09c0d1b78bc6f8753e10770e7045b08d50b4aa16a75b27a096d5ec1331f1fd0a44e95a8737c20240c90307b5497d3470393c2a00da0649e86d13e820591296c644fc1eef9e7c6ca4967c5e19df3153cd7fbd598c271e11c10397349ddc8cc8452ec", - &clock - ); - - t1.delete(); - t2.delete(); - t3.delete(); - t4.delete(); - - clock.destroy_for_testing(); - ts::return_shared(game); - ts.end(); - } - - #[test] - #[expected_failure(abort_code = games::vdf_based_lottery::EInvalidRandomness)] - fun test_empty_randomness() { - let user1 = @0x0; - - let mut ts = ts::begin(user1); - let clock = clock::create_for_testing(ts.ctx()); - - vdf_based_lottery::create(1000, 1000, &clock, ts.ctx()); - ts.next_tx(user1); - let mut game = ts.take_shared(); - - // User1 buys a ticket, but with wrong randomness length. - ts.next_tx(user1); - let t1 = game.participate(b"abcd", &clock, ts.ctx()); - - t1.delete(); - - sui::clock::destroy_for_testing(clock); - ts::return_shared(game); - ts.end(); - } -} diff --git a/sui_programmability/examples/move_tutorial/Move.toml b/sui_programmability/examples/move_tutorial/Move.toml deleted file mode 100644 index e0ce947a8a29c..0000000000000 --- a/sui_programmability/examples/move_tutorial/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "MyFirstPackage" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -my_first_package = "0x0" diff --git a/sui_programmability/examples/move_tutorial/sources/my_module.move b/sui_programmability/examples/move_tutorial/sources/my_module.move deleted file mode 100644 index b9ada5fd69f50..0000000000000 --- a/sui_programmability/examples/move_tutorial/sources/my_module.move +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module my_first_package::my_module { - - // Part 1: These imports are provided by default - // use sui::object::{Self, UID}; - // use sui::transfer; - // use sui::tx_context::{Self, TxContext}; - - // Part 2: struct definitions - public struct Sword has key, store { - id: UID, - magic: u64, - strength: u64, - } - - public struct Forge has key { - id: UID, - swords_created: u64, - } - - // Part 3: Module initializer to be executed when this module is published - fun init(ctx: &mut TxContext) { - let admin = Forge { - id: object::new(ctx), - swords_created: 0, - }; - - // Transfer the forge object to the module/package publisher - transfer::transfer(admin, ctx.sender()); - } - - // Part 4: Accessors required to read the struct fields - public fun magic(self: &Sword): u64 { - self.magic - } - - public fun strength(self: &Sword): u64 { - self.strength - } - - public fun swords_created(self: &Forge): u64 { - self.swords_created - } - - // Part 5: Public/entry functions (introduced later in the tutorial) - public fun sword_create(magic: u64, strength: u64, ctx: &mut TxContext): Sword { - // Create a sword - Sword { - id: object::new(ctx), - magic: magic, - strength: strength, - } - } - - /// Constructor for creating swords - public fun new_sword( - forge: &mut Forge, - magic: u64, - strength: u64, - ctx: &mut TxContext, - ): Sword { - forge.swords_created = forge.swords_created + 1; - Sword { - id: object::new(ctx), - magic: magic, - strength: strength, - } - } - - // Part 6: Tests - #[test] - fun test_sword_create() { - // Create a dummy TxContext for testing - let mut ctx = tx_context::dummy(); - - // Create a sword - let sword = Sword { - id: object::new(&mut ctx), - magic: 42, - strength: 7, - }; - - // Check if accessor functions return correct values - assert!(sword.magic() == 42 && sword.strength() == 7, 1); - - // Create a dummy address and transfer the sword - let dummy_address = @0xCAFE; - transfer::public_transfer(sword, dummy_address); - } - - #[test] - fun test_sword_transactions() { - use sui::test_scenario; - - // Create test addresses representing users - let initial_owner = @0xCAFE; - let final_owner = @0xFACE; - - // First transaction executed by initial owner to create the sword - let mut scenario = test_scenario::begin(initial_owner); - { - // Create the sword and transfer it to the initial owner - let sword = sword_create(42, 7, scenario.ctx()); - transfer::public_transfer(sword, initial_owner); - }; - - // Second transaction executed by the initial sword owner - scenario.next_tx(initial_owner); - { - // Extract the sword owned by the initial owner - let sword = scenario.take_from_sender(); - // Transfer the sword to the final owner - transfer::public_transfer(sword, final_owner); - }; - - // Third transaction executed by the final sword owner - scenario.next_tx(final_owner); - { - // Extract the sword owned by the final owner - let sword = scenario.take_from_sender(); - // Verify that the sword has expected properties - assert!(sword.magic() == 42 && sword.strength() == 7, 1); - // Return the sword to the object pool (it cannot be simply "dropped") - scenario.return_to_sender(sword) - }; - scenario.end(); - } - - #[test] - fun test_module_init() { - use sui::test_scenario; - - // Create test addresses representing users - let admin = @0xAD; - let initial_owner = @0xCAFE; - - // First transaction to emulate module initialization - let mut scenario = test_scenario::begin(admin); - { - init(scenario.ctx()); - }; - - // Second transaction to check if the forge has been created - // and has initial value of zero swords created - scenario.next_tx(admin); - { - // Extract the Forge object - let forge = scenario.take_from_sender(); - // Verify number of created swords - assert!(forge.swords_created() == 0, 1); - // Return the Forge object to the object pool - scenario.return_to_sender(forge); - }; - - // Third transaction executed by admin to create the sword - scenario.next_tx(admin); - { - let mut forge = scenario.take_from_sender(); - // Create the sword and transfer it to the initial owner - let sword = forge.new_sword(42, 7, scenario.ctx()); - transfer::public_transfer(sword, initial_owner); - scenario.return_to_sender(forge); - }; - scenario.end(); - } -} diff --git a/sui_programmability/examples/multi_package/README.md b/sui_programmability/examples/multi_package/README.md deleted file mode 100644 index f8cb10b284dd5..0000000000000 --- a/sui_programmability/examples/multi_package/README.md +++ /dev/null @@ -1,5 +0,0 @@ -An example of how to structure your code to include another user-level package as a dependency. - -Make sure that the dependency name (in this example DepPackage in main_package/Move.toml file's -`[dependencies]` section) is the same as the name of the package (in dep_package/Move.toml file's -`[package]` section). diff --git a/sui_programmability/examples/multi_package/dep_package/Move.toml b/sui_programmability/examples/multi_package/dep_package/Move.toml deleted file mode 100644 index c7a0a7bf5805b..0000000000000 --- a/sui_programmability/examples/multi_package/dep_package/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "DepPackage" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -dep_package = "0x0" diff --git a/sui_programmability/examples/multi_package/dep_package/sources/dep_module.move b/sui_programmability/examples/multi_package/dep_package/sources/dep_module.move deleted file mode 100644 index 5f39a4d557dd9..0000000000000 --- a/sui_programmability/examples/multi_package/dep_package/sources/dep_module.move +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module dep_package::dep_module { - - public fun foo(): u64 { - 42 - } - -} diff --git a/sui_programmability/examples/multi_package/main_package/Move.toml b/sui_programmability/examples/multi_package/main_package/Move.toml deleted file mode 100644 index f220f8d07c53a..0000000000000 --- a/sui_programmability/examples/multi_package/main_package/Move.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "MainPackage" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../../crates/sui-framework/packages/sui-framework" } -DepPackage = { local = "../dep_package" } - -[addresses] -main_package = "0x0" diff --git a/sui_programmability/examples/multi_package/main_package/sources/main_module.move b/sui_programmability/examples/multi_package/main_package/sources/main_module.move deleted file mode 100644 index 5ab8350df17b2..0000000000000 --- a/sui_programmability/examples/multi_package/main_package/sources/main_module.move +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[allow(unused_function)] -module main_package::main_module { - use dep_package::dep_module; - - fun foo(): u64 { - dep_module::foo() - } - -} diff --git a/sui_programmability/examples/nfts/Move.toml b/sui_programmability/examples/nfts/Move.toml deleted file mode 100644 index 03fd053678170..0000000000000 --- a/sui_programmability/examples/nfts/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "NFTs" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -nfts = "0x0" diff --git a/sui_programmability/examples/nfts/README.md b/sui_programmability/examples/nfts/README.md deleted file mode 100644 index 6ffc53c203c15..0000000000000 --- a/sui_programmability/examples/nfts/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# NFTs - -* Num: Issuing the first ten natural numbers as collectible NFT's. -* Marketplace: An opensea-like marketplace built with shared objects. -* geniteam: NFTs representing collectible monsters and cosmetics used in a farming game. -* Auction: example implementation of an [English auction](https://en.wikipedia.org/wiki/English_auction) using single-owner objects only. -* SharedAuction: example implementation of an [English auction](https://en.wikipedia.org/wiki/English_auction) using shared objects. -* ImageNFT: an NFT wrapping a URL pointing to an image stored off-chain (coming in future). diff --git a/sui_programmability/examples/nfts/sources/auction.move b/sui_programmability/examples/nfts/sources/auction.move deleted file mode 100644 index 02c27f764ec92..0000000000000 --- a/sui_programmability/examples/nfts/sources/auction.move +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This module is an implementation of an English auction -/// (https://en.wikipedia.org/wiki/English_auction) using single-owner -/// objects only. There are three types of parties participating in an -/// auction: -/// - auctioneer - a trusted party that runs the auction -/// - owner - the original owner of an item that is sold at an -/// auction; the owner submits a request to an auctioneer which runs -/// the auction -/// - bidders - parties interested in purchasing items sold -/// at an auction; they submit bids to an auctioneer to affect the -/// state of an auction -/// -/// A typical lifetime of an auction looks as follows: -/// - The auction starts with the owner sending an item to be sold along with -/// its own address to the auctioneer who creates and initializes an -/// auction. -/// - Bidders send their bid to the auctioneer. -/// A bid consists of the funds offered for the item and the bidder's address. -/// - The auctioneer periodically inspects the bids: -/// - (inspected bid > current best bid (initially there is no bid)): -/// The auctioneer updates the auction with the current bid -/// and the funds of the previous highest bid are sent back to their owner. -/// - (inspected bid <= current best bid): -/// The auctioneer sents the inspected bid's funds back to the new bidder, -/// and the auction remains unchanged. -/// - The auctioneer eventually ends the auction: -/// - if no bids were received, the item goes back to the original owner -/// - otherwise the funds accumulated in the auction go to the -/// original owner and the item goes to the bidder that won the auction - -module nfts::auction { - use sui::coin::{Self, Coin}; - use sui::balance::Balance; - use sui::sui::SUI; - - use nfts::auction_lib::{Self, Auction}; - - // Error codes. - - /// A bid submitted for the wrong (e.g. non-existent) auction. - const EWrongAuction: u64 = 1; - - /// Represents a bid sent by a bidder to the auctioneer. - public struct Bid has key { - id: UID, - /// Address of the bidder - bidder: address, - /// ID of the Auction object this bid is intended for - auction_id: ID, - /// Coin used for bidding. - bid: Balance - } - - // Entry functions. - - /// Creates an auction. It would be more natural to generate - /// auction_id in create_auction and be able to return it so that - /// it can be shared with bidders but we cannot do this at the - /// moment. This is executed by the owner of the asset to be - /// auctioned. - public fun create_auction( - to_sell: T, auctioneer: address, ctx: &mut TxContext - ): ID { - let auction = auction_lib::create_auction(to_sell, ctx); - let id = object::id(&auction); - auction_lib::transfer(auction, auctioneer); - id - } - - /// Creates a bid a and send it to the auctioneer along with the - /// ID of the auction. This is executed by a bidder. - public fun bid( - coin: Coin, auction_id: ID, auctioneer: address, ctx: &mut TxContext - ) { - let bid = Bid { - id: object::new(ctx), - bidder: tx_context::sender(ctx), - auction_id, - bid: coin::into_balance(coin), - }; - - transfer::transfer(bid, auctioneer); - } - - /// Updates the auction based on the information in the bid - /// (update auction if higher bid received and send coin back for - /// bids that are too low). This is executed by the auctioneer. - public entry fun update_auction( - auction: &mut Auction, bid: Bid, ctx: &mut TxContext - ) { - let Bid { id, bidder, auction_id, bid: balance } = bid; - assert!(object::borrow_id(auction) == &auction_id, EWrongAuction); - auction_lib::update_auction(auction, bidder, balance, ctx); - - object::delete(id); - } - - /// Ends the auction - transfers item to the currently highest - /// bidder or to the original owner if no bids have been - /// placed. This is executed by the auctioneer. - public entry fun end_auction( - auction: Auction, ctx: &mut TxContext - ) { - auction_lib::end_and_destroy_auction(auction, ctx); - } -} diff --git a/sui_programmability/examples/nfts/sources/auction_lib.move b/sui_programmability/examples/nfts/sources/auction_lib.move deleted file mode 100644 index 0c969e0e57cfa..0000000000000 --- a/sui_programmability/examples/nfts/sources/auction_lib.move +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This is a helper module for implementing two versions of an -/// English auction (https://en.wikipedia.org/wiki/English_auction), -/// one using single-owner objects only and the other using shared -/// objects. -module nfts::auction_lib { - use sui::coin; - use sui::balance::{Self, Balance}; - use sui::sui::SUI; - - /// Stores information about an auction bid. - public struct BidData has store { - /// Coin representing the current (highest) bid. - funds: Balance, - /// Address of the highest bidder. - highest_bidder: address, - } - - /// Maintains the state of the auction owned by a trusted - /// auctioneer. - public struct Auction has key { - id: UID, - /// Item to be sold. It only really needs to be wrapped in - /// Option if Auction represents a shared object but we do it - /// for single-owner Auctions for better code re-use. - to_sell: Option, - /// Owner of the time to be sold. - owner: address, - /// Data representing the highest bid (starts with no bid) - bid_data: Option, - } - - public(package) fun auction_owner(auction: &Auction): address { - auction.owner - } - - /// Creates an auction. This is executed by the owner of the asset to be - /// auctioned. - public(package) fun create_auction( - to_sell: T, ctx: &mut TxContext - ): Auction { - // A question one might asked is how do we know that to_sell - // is owned by the caller of this entry function and the - // answer is that it's checked by the runtime. - Auction { - id: object::new(ctx), - to_sell: option::some(to_sell), - owner: tx_context::sender(ctx), - bid_data: option::none(), - } - } - - /// Updates the auction based on the information in the bid - /// (update auction if higher bid received and send coin back for - /// bids that are too low). - public fun update_auction( - auction: &mut Auction, - bidder: address, - funds: Balance, - ctx: &mut TxContext, - ) { - if (option::is_none(&auction.bid_data)) { - // first bid - let bid_data = BidData { - funds, - highest_bidder: bidder, - }; - option::fill(&mut auction.bid_data, bid_data); - } else { - let prev_bid_data = option::borrow(&auction.bid_data); - if (balance::value(&funds) > balance::value(&prev_bid_data.funds)) { - // a bid higher than currently highest bid received - let new_bid_data = BidData { - funds, - highest_bidder: bidder - }; - - // update auction to reflect highest bid - let BidData { - funds, - highest_bidder - } = option::swap(&mut auction.bid_data, new_bid_data); - - // transfer previously highest bid to its bidder - send_balance(funds, highest_bidder, ctx); - } else { - // a bid is too low - return funds to the bidder - send_balance(funds, bidder, ctx); - } - } - } - - /// Ends the auction - transfers item to the currently highest - /// bidder or to the original owner if no bids have been placed. - fun end_auction( - to_sell: &mut Option, - owner: address, - bid_data: &mut Option, - ctx: &mut TxContext - ) { - let item = option::extract(to_sell); - if (option::is_some(bid_data)) { - // bids have been placed - send funds to the original item - // owner and the item to the highest bidder - let BidData { - funds, - highest_bidder - } = option::extract(bid_data); - - send_balance(funds, owner, ctx); - transfer::public_transfer(item, highest_bidder); - } else { - // no bids placed - send the item back to the original owner - transfer::public_transfer(item, owner); - }; - } - - /// Ends auction and destroys auction object (can only be used if - /// Auction is single-owner object) - transfers item to the - /// currently highest bidder or to the original owner if no bids - /// have been placed. - public fun end_and_destroy_auction( - auction: Auction, ctx: &mut TxContext - ) { - let Auction { id, mut to_sell, owner, mut bid_data } = auction; - object::delete(id); - - end_auction(&mut to_sell, owner, &mut bid_data, ctx); - - option::destroy_none(bid_data); - option::destroy_none(to_sell); - } - - /// Ends auction (should only be used if Auction is a shared - /// object) - transfers item to the currently highest bidder or to - /// the original owner if no bids have been placed. - public fun end_shared_auction( - auction: &mut Auction, ctx: &mut TxContext - ) { - end_auction(&mut auction.to_sell, auction.owner, &mut auction.bid_data, ctx); - } - - /// Helper for the most common operation - wrapping a balance and sending it - fun send_balance(balance: Balance, to: address, ctx: &mut TxContext) { - transfer::public_transfer(coin::from_balance(balance, ctx), to) - } - - /// exposes transfer::transfer - public fun transfer(obj: Auction, recipient: address) { - transfer::transfer(obj, recipient) - } - - #[allow(lint(share_owned))] - public fun share_object(obj: Auction) { - transfer::share_object(obj) - } -} diff --git a/sui_programmability/examples/nfts/sources/chat.move b/sui_programmability/examples/nfts/sources/chat.move deleted file mode 100644 index 7b455494f78b0..0000000000000 --- a/sui_programmability/examples/nfts/sources/chat.move +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module nfts::chat { - use std::ascii::{Self, String}; - use std::option::some; - use std::vector::length; - - /// Max text length. - const MAX_TEXT_LENGTH: u64 = 512; - - /// Text size overflow. - const ETextOverflow: u64 = 0; - - /// Sui Chat NFT (i.e., a post, retweet, like, chat message etc). - public struct Chat has key, store { - id: UID, - // The ID of the chat app. - app_id: address, - // Post's text. - text: String, - // Set if referencing an another object (i.e., due to a Like, Retweet, Reply etc). - // We allow referencing any object type, not only Chat NFTs. - ref_id: Option
, - // app-specific metadata. We do not enforce a metadata format and delegate this to app layer. - metadata: vector, - } - - /// Simple Chat.text getter. - public fun text(chat: &Chat): String { - chat.text - } - - #[allow(lint(self_transfer))] - /// Mint (post) a Chat object. - fun post_internal( - app_id: address, - text: vector, - ref_id: Option
, - metadata: vector, - ctx: &mut TxContext, - ) { - assert!(length(&text) <= MAX_TEXT_LENGTH, ETextOverflow); - let chat = Chat { - id: object::new(ctx), - app_id, - text: ascii::string(text), - ref_id, - metadata, - }; - transfer::public_transfer(chat, tx_context::sender(ctx)); - } - - /// Mint (post) a Chat object without referencing another object. - public entry fun post( - app_identifier: address, - text: vector, - metadata: vector, - ctx: &mut TxContext, - ) { - post_internal(app_identifier, text, option::none(), metadata, ctx); - } - - /// Mint (post) a Chat object and reference another object (i.e., to simulate retweet, reply, like, attach). - /// TODO: Using `address` as `app_identifier` & `ref_identifier` type, because we cannot pass `ID` to entry - /// functions. Using `vector` for `text` instead of `String` for the same reason. - public entry fun post_with_ref( - app_identifier: address, - text: vector, - ref_identifier: address, - metadata: vector, - ctx: &mut TxContext, - ) { - post_internal(app_identifier, text, some(ref_identifier), metadata, ctx); - } - - /// Burn a Chat object. - public entry fun burn(chat: Chat) { - let Chat { id, app_id: _, text: _, ref_id: _, metadata: _ } = chat; - object::delete(id); - } -} diff --git a/sui_programmability/examples/nfts/sources/cross_chain_airdrop.move b/sui_programmability/examples/nfts/sources/cross_chain_airdrop.move deleted file mode 100644 index b189719664184..0000000000000 --- a/sui_programmability/examples/nfts/sources/cross_chain_airdrop.move +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Allow a trusted oracle to mint a copy of NFT from a different chain. There can -/// only be one copy for each unique pair of contract_address and token_id. We only -/// support a single chain(Ethereum) right now, but this can be extended to other -/// chains by adding a chain_id field. -module nfts::cross_chain_airdrop { - use nfts::erc721_metadata::{Self, ERC721Metadata, TokenID}; - - /// The oracle manages one `PerContractAirdropInfo` for each Ethereum contract - public struct CrossChainAirdropOracle has key { - id: UID, - // TODO: replace this with SparseSet for O(1) on-chain uniqueness check - managed_contracts: vector, - } - - /// The address of the source contract - public struct SourceContractAddress has store, copy { - address: vector, - } - - /// Contains the Airdrop info for one contract address on Ethereum - public struct PerContractAirdropInfo has store { - /// A single contract address on Ethereum - source_contract_address: SourceContractAddress, - - /// The Ethereum token ids whose Airdrop has been claimed. These - /// are stored to prevent the same NFT from being claimed twice - // TODO: replace u64 with u256 once the latter is supported - // - // TODO: replace this with SparseSet for O(1) on-chain uniqueness check - claimed_source_token_ids: vector - } - - /// The Sui representation of the original ERC721 NFT on Eth - public struct ERC721 has key, store { - id: UID, - /// The address of the source contract, e.g, the Ethereum contract address - source_contract_address: SourceContractAddress, - /// The metadata associated with this NFT - metadata: ERC721Metadata, - } - - // Error codes - - /// Trying to claim a token that has already been claimed - const ETokenIDClaimed: u64 = 0; - - #[allow(unused_function)] - /// Create the `Orcacle` capability and hand it off to the oracle - /// TODO: To make this usable, the oracle should be sent to a - /// hardcoded address that the contract creator has private key. - fun init(ctx: &mut TxContext) { - transfer::transfer( - CrossChainAirdropOracle { - id: object::new(ctx), - managed_contracts: vector::empty(), - }, - tx_context::sender(ctx) - ) - } - - /// Called by the oracle to mint the airdrop NFT and transfer to the recipient - public entry fun claim( - oracle: &mut CrossChainAirdropOracle, - recipient: address, - source_contract_address: vector, - source_token_id: u64, - name: vector, - token_uri: vector, - ctx: &mut TxContext, - ) { - let contract = get_or_create_contract(oracle, &source_contract_address); - let token_id = erc721_metadata::new_token_id(source_token_id); - // NOTE: this is where the globally uniqueness check happens - assert!(!is_token_claimed(contract, &token_id), ETokenIDClaimed); - let nft = ERC721 { - id: object::new(ctx), - source_contract_address: SourceContractAddress { address: source_contract_address }, - metadata: erc721_metadata::new(token_id, name, token_uri), - }; - vector::push_back(&mut contract.claimed_source_token_ids, token_id); - transfer::public_transfer(nft, recipient) - } - - fun get_or_create_contract(oracle: &mut CrossChainAirdropOracle, source_contract_address: &vector): &mut PerContractAirdropInfo { - let mut index = 0; - // TODO: replace this with SparseSet so that the on-chain uniqueness check can be O(1) - while (index < vector::length(&oracle.managed_contracts)) { - let id = vector::borrow_mut(&mut oracle.managed_contracts, index); - if (&id.source_contract_address.address == source_contract_address) { - return id - }; - index = index + 1; - }; - - create_contract(oracle, source_contract_address) - } - - fun create_contract(oracle: &mut CrossChainAirdropOracle, source_contract_address: &vector): &mut PerContractAirdropInfo { - let id = PerContractAirdropInfo { - source_contract_address: SourceContractAddress { address: *source_contract_address }, - claimed_source_token_ids: vector::empty() - }; - vector::push_back(&mut oracle.managed_contracts, id); - let idx = vector::length(&oracle.managed_contracts) - 1; - vector::borrow_mut(&mut oracle.managed_contracts, idx) - } - - fun is_token_claimed(contract: &PerContractAirdropInfo, source_token_id: &TokenID): bool { - // TODO: replace this with SparseSet so that the on-chain uniqueness check can be O(1) - let mut index = 0; - while (index < vector::length(&contract.claimed_source_token_ids)) { - let claimed_id = vector::borrow(&contract.claimed_source_token_ids, index); - if (claimed_id == source_token_id) { - return true - }; - index = index + 1; - }; - false - } - - #[test_only] - /// Wrapper of module initializer for testing - public fun test_init(ctx: &mut TxContext) { - init(ctx) - } -} - -module nfts::erc721_metadata { - use std::ascii; - use sui::url::{Self, Url}; - use std::string; - - // TODO: add symbol()? - /// A wrapper type for the ERC721 metadata standard https://eips.ethereum.org/EIPS/eip-721 - public struct ERC721Metadata has store { - /// The token id associated with the source contract on Ethereum - token_id: TokenID, - /// A descriptive name for a collection of NFTs in this contract. - /// This corresponds to the `name()` method in the - /// ERC721Metadata interface in EIP-721. - name: string::String, - /// A distinct Uniform Resource Identifier (URI) for a given asset. - /// This corresponds to the `tokenURI()` method in the ERC721Metadata - /// interface in EIP-721. - token_uri: Url, - } - - // TODO: replace u64 with u256 once the latter is supported - // - /// An ERC721 token ID - public struct TokenID has store, copy { - id: u64, - } - - /// Construct a new ERC721Metadata from the given inputs. Does not perform any validation - /// on `token_uri` or `name` - public fun new(token_id: TokenID, name: vector, token_uri: vector): ERC721Metadata { - // Note: this will abort if `token_uri` is not valid ASCII - let uri_str = ascii::string(token_uri); - ERC721Metadata { - token_id, - name: string::utf8(name), - token_uri: url::new_unsafe(uri_str), - } - } - - public fun new_token_id(id: u64): TokenID { - TokenID { id } - } - - public fun token_id(self: &ERC721Metadata): &TokenID { - &self.token_id - } - - public fun token_uri(self: &ERC721Metadata): &Url { - &self.token_uri - } - - public fun name(self: &ERC721Metadata): &string::String { - &self.name - } -} diff --git a/sui_programmability/examples/nfts/sources/devnet_nft.move b/sui_programmability/examples/nfts/sources/devnet_nft.move deleted file mode 100644 index e6ae716e79029..0000000000000 --- a/sui_programmability/examples/nfts/sources/devnet_nft.move +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -// TODO: consider renaming this to `example_nft` -/// A minimalist example to demonstrate how to create an NFT like object -/// on Sui. -module nfts::devnet_nft { - use sui::url::{Self, Url}; - use std::string; - use sui::event; - - /// An example NFT that can be minted by anybody - public struct DevNetNFT has key, store { - id: UID, - /// Name for the token - name: string::String, - /// Description of the token - description: string::String, - /// URL for the token - url: Url, - // TODO: allow custom attributes - } - - public struct MintNFTEvent has copy, drop { - // The Object ID of the NFT - object_id: ID, - // The creator of the NFT - creator: address, - // The name of the NFT - name: string::String, - } - - /// Create a new devnet_nft - public entry fun mint( - name: vector, - description: vector, - url: vector, - ctx: &mut TxContext - ) { - let nft = DevNetNFT { - id: object::new(ctx), - name: string::utf8(name), - description: string::utf8(description), - url: url::new_unsafe_from_bytes(url) - }; - let sender = tx_context::sender(ctx); - event::emit(MintNFTEvent { - object_id: object::uid_to_inner(&nft.id), - creator: sender, - name: nft.name, - }); - transfer::public_transfer(nft, sender); - } - - /// Update the `description` of `nft` to `new_description` - public entry fun update_description( - nft: &mut DevNetNFT, - new_description: vector, - ) { - nft.description = string::utf8(new_description) - } - - /// Permanently delete `nft` - public entry fun burn(nft: DevNetNFT) { - let DevNetNFT { id, name: _, description: _, url: _ } = nft; - object::delete(id) - } - - /// Get the NFT's `name` - public fun name(nft: &DevNetNFT): &string::String { - &nft.name - } - - /// Get the NFT's `description` - public fun description(nft: &DevNetNFT): &string::String { - &nft.description - } - - /// Get the NFT's `url` - public fun url(nft: &DevNetNFT): &Url { - &nft.url - } -} - -#[test_only] -module nfts::devnet_nftTests { - use nfts::devnet_nft::{Self, DevNetNFT}; - use sui::test_scenario as ts; - use std::string; - - #[test] - fun mint_transfer_update() { - let addr1 = @0xA; - let addr2 = @0xB; - // create the NFT - let mut scenario = ts::begin(addr1); - { - devnet_nft::mint(b"test", b"a test", b"https://www.sui.io", ts::ctx(&mut scenario)) - }; - // send it from A to B - ts::next_tx(&mut scenario, addr1); - { - let nft = ts::take_from_sender(&scenario); - transfer::public_transfer(nft, addr2); - }; - // update its description - ts::next_tx(&mut scenario, addr2); - { - let mut nft = ts::take_from_sender(&scenario); - devnet_nft::update_description(&mut nft, b"a new description") ; - assert!(*string::as_bytes(devnet_nft::description(&nft)) == b"a new description", 0); - ts::return_to_sender(&scenario, nft); - }; - // burn it - ts::next_tx(&mut scenario, addr2); - { - let nft = ts::take_from_sender(&scenario); - devnet_nft::burn(nft) - }; - ts::end(scenario); - } -} diff --git a/sui_programmability/examples/nfts/sources/discount_coupon.move b/sui_programmability/examples/nfts/sources/discount_coupon.move deleted file mode 100644 index 4f1f8290688f5..0000000000000 --- a/sui_programmability/examples/nfts/sources/discount_coupon.move +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module nfts::discount_coupon { - use sui::coin; - use sui::sui::SUI; - - /// Sending to wrong recipient. - const EWrongRecipient: u64 = 0; - - /// Percentage discount out of range. - const EOutOfRangeDiscount: u64 = 1; - - /// Discount coupon NFT. - public struct DiscountCoupon has key { - id: UID, - // coupon issuer - issuer: address, - // percentage discount [1-100] - discount: u8, - // expiration timestamp (UNIX time) - app specific - expiration: u64, - } - - /// Simple issuer getter. - public fun issuer(coupon: &DiscountCoupon): address { - coupon.issuer - } - - /// Mint then transfer a new `DiscountCoupon` NFT, and top up recipient with some SUI. - public entry fun mint_and_topup( - coin: coin::Coin, - discount: u8, - expiration: u64, - recipient: address, - ctx: &mut TxContext, - ) { - assert!(discount > 0 && discount <= 100, EOutOfRangeDiscount); - let coupon = DiscountCoupon { - id: object::new(ctx), - issuer: tx_context::sender(ctx), - discount, - expiration, - }; - transfer::transfer(coupon, recipient); - transfer::public_transfer(coin, recipient); - } - - /// Burn DiscountCoupon. - public entry fun burn(nft: DiscountCoupon) { - let DiscountCoupon { id, issuer: _, discount: _, expiration: _ } = nft; - object::delete(id); - } - - /// Transfer DiscountCoupon to issuer only. - // TODO: Consider adding more valid recipients. - // If we stick with issuer-as-receiver only, then `recipient` input won't be required). - public entry fun transfer(coupon: DiscountCoupon, recipient: address) { - assert!(&coupon.issuer == &recipient, EWrongRecipient); - transfer::transfer(coupon, recipient); - } -} diff --git a/sui_programmability/examples/nfts/sources/geniteam.move b/sui_programmability/examples/nfts/sources/geniteam.move deleted file mode 100644 index 48855920d97b4..0000000000000 --- a/sui_programmability/examples/nfts/sources/geniteam.move +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module nfts::geniteam { - use sui::object_bag::{Self, ObjectBag}; - use sui::object_table::{Self, ObjectTable}; - use sui::dynamic_object_field; - use nfts::typed_id::{Self, TypedID}; - use std::ascii::{Self, String}; - - /// Trying to add more than 1 farm to a Player - const ETooManyFarms: u64 = 1; - - /// Invalid cosmetic slot - const EInvalidCosmeticsSlot: u64 = 4; - - public struct Player has key { - id: UID, - player_name: String, - water_runes_count: u64, - fire_runes_count: u64, - wind_runes_count: u64, - earth_runes_count: u64, - - // Owned Farm - owned_farm: Option>, - - // Inventory of unassigned cosmetics. - // A cosmetic can be either a FarmCosmetic or a MonsterCosmetic. - // Since they can be of different types, we use Bag instead of Collection. - inventory: ObjectBag, - } - - public struct Farm has key, store { - id: UID, - farm_name: String, - farm_img_index: u64, - level: u64, - current_xp: u64, - total_monster_slots: u64, - occupied_monster_slots: u64, - - // Collection of Pet monsters owned by this Farm - pet_monsters: ObjectTable, - - // Applied cosmetic at this slot - applied_farm_cosmetic_0: Option>, - // Applied cosmetic at this slot - applied_farm_cosmetic_1: Option>, - } - - public struct Monster has key, store { - id: UID, - monster_name: String, - monster_img_index: u64, - breed: u8, - monster_affinity: u8, - monster_description: String, - monster_level: u64, - monster_xp: u64, - hunger_level: u64, - affection_level: u64, - buddy_level: u8, - display: String, - - // Applied cosmetic at this slot - applied_monster_cosmetic_0: Option>, - // Applied cosmetic at this slot - applied_monster_cosmetic_1: Option>, - - } - - public struct FarmCosmetic has key, store{ - id: UID, - cosmetic_type: u8, - display: String, - } - - public struct MonsterCosmetic has key, store { - id: UID, - cosmetic_type: u8, - display: String, - } - - // ============================ Entry functions ============================ - - /// Create a player and transfer it to the transaction sender - public entry fun create_player( - player_name: vector, ctx: &mut TxContext - ) { - // Create player simply and transfer to caller - let player = new_player(player_name, ctx); - transfer::transfer(player, tx_context::sender(ctx)) - } - - /// Create a Farm and add it to the Player - public entry fun create_farm( - player: &mut Player, farm_img_index: u64, farm_name: vector, - total_monster_slots: u64, ctx: &mut TxContext - ) { - // We only allow one farm for now - assert!(option::is_none(&player.owned_farm), ETooManyFarms); - - let farm = new_farm(farm_name, farm_img_index, total_monster_slots, ctx); - let farm_id = typed_id::new(&farm); - - // Transfer ownership of farm to player - dynamic_object_field::add(&mut player.id, farm_id, farm); - - // Store the farm - option::fill(&mut player.owned_farm, farm_id) - } - - /// Create a Monster and add it to the Farm's collection of Monsters, which - /// is unbounded - public entry fun create_monster(player: &mut Player, - monster_name: vector, - monster_img_index: u64, - breed: u8, - monster_affinity: u8, - monster_description: vector, - display: vector, - ctx: &mut TxContext - ) { - let monster = new_monster( - monster_name, - monster_img_index, - breed, - monster_affinity, - monster_description, - display, - ctx - ); - let id = object::id(&monster); - - let farm_id = *option::borrow(&player.owned_farm); - let farm: &mut Farm = dynamic_object_field::borrow_mut(&mut player.id, farm_id); - // TODO: Decouple adding monster to farm from creating a monster. - // Add it to the collection - object_table::add(&mut farm.pet_monsters, id, monster); - } - - /// Create Farm cosmetic owned by player and add to its inventory - public fun create_farm_cosmetics( - player: &mut Player, cosmetic_type: u8, - display: vector, ctx: &mut TxContext - ) { - // Create the farm cosmetic object - let farm_cosmetic = FarmCosmetic { - id: object::new(ctx), - cosmetic_type, - display: ascii::string(display) - }; - - // Add it to the player's inventory - object_bag::add(&mut player.inventory, object::id(&farm_cosmetic), farm_cosmetic); - } - - /// Create Monster cosmetic owned by player and add to its inventory - public fun create_monster_cosmetics( - player: &mut Player, cosmetic_type: u8, - display: vector, ctx: &mut TxContext - ) { - // Create the farm cosmetic object - let monster_cosmetic = MonsterCosmetic { - id: object::new(ctx), - cosmetic_type, - display: ascii::string(display) - }; - - // Add it to the player's inventory - object_bag::add(&mut player.inventory, object::id(&monster_cosmetic), monster_cosmetic); - } - - /// Update the attributes of a player - public fun update_player( - player: &mut Player, - water_runes_count: u64, - fire_runes_count: u64, - wind_runes_count: u64, - earth_runes_count: u64, - ) { - player.water_runes_count = water_runes_count; - player.fire_runes_count = fire_runes_count; - player.wind_runes_count = wind_runes_count; - player.earth_runes_count = earth_runes_count - } - - /// Update the attributes of a monster - public fun update_monster_stats( - player: &mut Player, - monster_id: ID, - monster_affinity: u8, - monster_level: u64, - hunger_level: u64, - affection_level: u64, - buddy_level: u8, - display: vector, - ) { - let farm_id = *option::borrow(&player.owned_farm); - let farm: &mut Farm = dynamic_object_field::borrow_mut(&mut player.id, farm_id); - let monster = object_table::borrow_mut(&mut farm.pet_monsters, monster_id); - monster.monster_affinity = monster_affinity; - monster.monster_level = monster_level; - monster.hunger_level = hunger_level; - monster.affection_level = affection_level; - monster.buddy_level = buddy_level; - if (vector::length(&display) != 0) { - monster.display = ascii::string(display); - } - } - - - /// Update the attributes of the farm - public fun update_farm_stats( - _player: &mut Player, farm: &mut Farm, level: u64, current_xp: u64, - ) { - farm.current_xp = current_xp; - farm.level = level; - } - - /// Apply the cosmetic to the Farm from the inventory - public fun update_farm_cosmetics( - player: &mut Player, - farm_cosmetic: FarmCosmetic, cosmetic_slot_id: u64 - ) { - // Only 2 slots allowed - assert!(cosmetic_slot_id <= 1 , EInvalidCosmeticsSlot); - - // Transfer ownership of cosmetic to this farm - let farm_id = *option::borrow(&player.owned_farm); - let farm: &mut Farm = dynamic_object_field::borrow_mut(&mut player.id, farm_id); - let child_ref = typed_id::new(&farm_cosmetic); - dynamic_object_field::add(&mut farm.id, child_ref, farm_cosmetic); - - // Assign by slot - if (cosmetic_slot_id == 0) { - // Store the cosmetic - option::fill(&mut farm.applied_farm_cosmetic_0, child_ref) - } else { - // Store the cosmetic - option::fill(&mut farm.applied_farm_cosmetic_1, child_ref) - }; - } - - /// Apply the cosmetics to the Monster from the inventory - public fun update_monster_cosmetics( - player: &mut Player, monster_id: ID, monster_cosmetic: MonsterCosmetic, cosmetic_slot_id: u64, - ) { - // Only 2 slots allowed - assert!(cosmetic_slot_id <= 1 , EInvalidCosmeticsSlot); - - let farm_id = *option::borrow(&player.owned_farm); - let farm: &mut Farm = dynamic_object_field::borrow_mut(&mut player.id, farm_id); - let monster = object_table::borrow_mut(&mut farm.pet_monsters, monster_id); - // Transfer ownership of cosmetic to this monster - let child_ref = typed_id::new(&monster_cosmetic); - dynamic_object_field::add(&mut monster.id, child_ref, monster_cosmetic); - - // Assign by slot - if (cosmetic_slot_id == 0) { - // Store the cosmetic - option::fill(&mut monster.applied_monster_cosmetic_0, child_ref) - } else { - // Store the cosmetic - option::fill(&mut monster.applied_monster_cosmetic_1, child_ref) - }; - } - - // ============== Constructors. These create new Sui objects. ============== - - // Constructs a new basic Player object - fun new_player( - player_name: vector, ctx: &mut TxContext - ): Player { - // Create a new id for player. - let id = object::new(ctx); - - // Create inventory collection. - let inventory = object_bag::new(ctx); - - Player { - id, - player_name: ascii::string(player_name), - water_runes_count: 0, - fire_runes_count: 0, - wind_runes_count: 0, - earth_runes_count: 0, - owned_farm: option::none(), - inventory, - } - } - - // Constructs a new basic Farm object - fun new_farm( - farm_name: vector, farm_img_index: u64, total_monster_slots: u64, - ctx: &mut TxContext - ): Farm { - // Create a new id for farm. - let id = object::new(ctx); - - // Create pet monsters collection. - let pet_monsters = object_table::new(ctx); - - Farm { - id, - farm_name: ascii::string(farm_name), - total_monster_slots, - farm_img_index, - level: 0, - current_xp: 0, - occupied_monster_slots: 0, - pet_monsters, - applied_farm_cosmetic_0: option::none(), - applied_farm_cosmetic_1: option::none(), - } - } - - // Constructs a new basic Monster object - fun new_monster( - monster_name: vector, - monster_img_index: u64, - breed: u8, - monster_affinity: u8, - monster_description: vector, - display: vector, - ctx: &mut TxContext - ): Monster { - - Monster { - id: object::new(ctx), - monster_name: ascii::string(monster_name), - monster_img_index, - breed, - monster_affinity, - monster_description: ascii::string(monster_description), - monster_level: 0, - monster_xp: 0, - hunger_level: 0, - affection_level: 0, - buddy_level: 0, - display: ascii::string(display), - applied_monster_cosmetic_0: option::none(), - applied_monster_cosmetic_1: option::none(), - } - } -} - -// temp duplicate to unblock -module nfts::typed_id { - /// An ID of an of type `T`. See `ID` for more details - /// By construction, it is guaranteed that the `ID` represents an object of type `T` - public struct TypedID has copy, drop, store { - id: ID, - } - - /// Get the underlying `ID` of `obj`, and remember the type - public fun new(obj: &T): TypedID { - TypedID { id: object::id(obj) } - } - - /// Borrow the inner `ID` of `typed_id` - public fun as_id(typed_id: &TypedID): &ID { - &typed_id.id - } - - /// Get the inner `ID` of `typed_id` - public fun to_id(typed_id: TypedID): ID { - let TypedID { id } = typed_id; - id - } - - /// Check that underlying `ID` in the `typed_id` equals the objects ID - public fun equals_object(typed_id: &TypedID, obj: &T): bool { - typed_id.id == object::id(obj) - } -} diff --git a/sui_programmability/examples/nfts/sources/marketplace.move b/sui_programmability/examples/nfts/sources/marketplace.move deleted file mode 100644 index 569554776a2ff..0000000000000 --- a/sui_programmability/examples/nfts/sources/marketplace.move +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Basic `Marketplace` implementation. Supports listing of any assets, -/// and does not have constraints. -/// -/// Makes use of `sui::dynamic_object_field` module by attaching `Listing` -/// objects as fields to the `Marketplace` object; as well as stores and -/// merges user profits as dynamic object fields (ofield). -/// -/// Rough illustration of the dynamic field architecture for listings: -/// ``` -/// /--->Listing--->Item -/// (Marketplace)--->Listing--->Item -/// \--->Listing--->Item -/// ``` -/// -/// Profits storage is also attached to the `Marketplace` (indexed by `address`): -/// ``` -/// /--->Coin -/// (Marketplace)--->Coin -/// \--->Coin -/// ``` -module nfts::marketplace { - use sui::dynamic_object_field as ofield; - use sui::coin::{Self, Coin}; - - /// For when amount paid does not match the expected. - const EAmountIncorrect: u64 = 0; - /// For when someone tries to delist without ownership. - const ENotOwner: u64 = 1; - - /// A shared `Marketplace`. Can be created by anyone using the - /// `create` function. One instance of `Marketplace` accepts - /// only one type of Coin - `COIN` for all its listings. - public struct Marketplace has key { - id: UID, - } - - /// A single listing which contains the listed item and its - /// price in [`Coin`]. - public struct Listing has key, store { - id: UID, - ask: u64, - owner: address, - } - - /// Create a new shared Marketplace. - public entry fun create(ctx: &mut TxContext) { - let id = object::new(ctx); - transfer::share_object(Marketplace { id }) - } - - /// List an item at the Marketplace. - public entry fun list( - marketplace: &mut Marketplace, - item: T, - ask: u64, - ctx: &mut TxContext - ) { - let item_id = object::id(&item); - let mut listing = Listing { - ask, - id: object::new(ctx), - owner: tx_context::sender(ctx), - }; - - ofield::add(&mut listing.id, true, item); - ofield::add(&mut marketplace.id, item_id, listing) - } - - /// Remove listing and get an item back. Only owner can do that. - public fun delist( - marketplace: &mut Marketplace, - item_id: ID, - ctx: &TxContext - ): T { - let Listing { - mut id, - owner, - ask: _, - } = ofield::remove(&mut marketplace.id, item_id); - - assert!(tx_context::sender(ctx) == owner, ENotOwner); - - let item = ofield::remove(&mut id, true); - object::delete(id); - item - } - - /// Call [`delist`] and transfer item to the sender. - public entry fun delist_and_take( - marketplace: &mut Marketplace, - item_id: ID, - ctx: &TxContext - ) { - let item = delist(marketplace, item_id, ctx); - transfer::public_transfer(item, tx_context::sender(ctx)); - } - - /// Purchase an item using a known Listing. Payment is done in Coin. - /// Amount paid must match the requested amount. If conditions are met, - /// owner of the item gets the payment and buyer receives their item. - public fun buy( - marketplace: &mut Marketplace, - item_id: ID, - paid: Coin, - ): T { - let Listing { - mut id, - ask, - owner - } = ofield::remove(&mut marketplace.id, item_id); - - assert!(ask == coin::value(&paid), EAmountIncorrect); - - // Check if there's already a Coin hanging and merge `paid` with it. - // Otherwise attach `paid` to the `Marketplace` under owner's `address`. - if (ofield::exists_
(&marketplace.id, owner)) { - coin::join( - ofield::borrow_mut>(&mut marketplace.id, owner), - paid - ) - } else { - ofield::add(&mut marketplace.id, owner, paid) - }; - - let item = ofield::remove(&mut id, true); - object::delete(id); - item - } - - /// Call [`buy`] and transfer item to the sender. - public entry fun buy_and_take( - marketplace: &mut Marketplace, - item_id: ID, - paid: Coin, - ctx: &TxContext - ) { - transfer::public_transfer( - buy(marketplace, item_id, paid), - tx_context::sender(ctx) - ) - } - - /// Take profits from selling items on the `Marketplace`. - public fun take_profits( - marketplace: &mut Marketplace, - ctx: &TxContext - ): Coin { - ofield::remove>(&mut marketplace.id, tx_context::sender(ctx)) - } - - /// Call [`take_profits`] and transfer Coin to the sender. - public entry fun take_profits_and_keep( - marketplace: &mut Marketplace, - ctx: &TxContext - ) { - transfer::public_transfer( - take_profits(marketplace, ctx), - tx_context::sender(ctx) - ) - } -} - -#[test_only] -module nfts::marketplaceTests { - use sui::coin; - use sui::sui::SUI; - use sui::test_scenario::{Self, Scenario}; - use nfts::marketplace; - - // Simple Kitty-NFT data structure. - public struct Kitty has key, store { - id: UID, - kitty_id: u8 - } - - const ADMIN: address = @0xA55; - const SELLER: address = @0x00A; - const BUYER: address = @0x00B; - - #[allow(unused_function)] - /// Create a shared [`Marketplace`]. - fun create_marketplace(scenario: &mut Scenario) { - test_scenario::next_tx(scenario, ADMIN); - marketplace::create(test_scenario::ctx(scenario)); - } - - #[allow(unused_function)] - /// Mint SUI and send it to BUYER. - fun mint_some_coin(scenario: &mut Scenario) { - test_scenario::next_tx(scenario, ADMIN); - let coin = coin::mint_for_testing(1000, test_scenario::ctx(scenario)); - transfer::public_transfer(coin, BUYER); - } - - #[allow(unused_function)] - /// Mint Kitty NFT and send it to SELLER. - fun mint_kitty(scenario: &mut Scenario) { - test_scenario::next_tx(scenario, ADMIN); - let nft = Kitty { id: object::new(test_scenario::ctx(scenario)), kitty_id: 1 }; - transfer::public_transfer(nft, SELLER); - } - - // TODO(dyn-child) redo test with dynamic child object loading - // // SELLER lists Kitty at the Marketplace for 100 SUI. - // fun list_kitty(scenario: &mut Scenario) { - // test_scenario::next_tx(scenario, SELLER); - // let mkp_val = test_scenario::take_shared(scenario); - // let mkp = &mut mkp_val; - // let bag = test_scenario::take_child_object(scenario, mkp); - // let nft = test_scenario::take_from_sender(scenario); - - // marketplace::list(mkp, &mut bag, nft, 100, test_scenario::ctx(scenario)); - // test_scenario::return_shared(mkp_val); - // test_scenario::return_to_sender(scenario, bag); - // } - - // TODO(dyn-child) redo test with dynamic child object loading - // #[test] - // fun list_and_delist() { - // let scenario = &mut test_scenario::begin(ADMIN); - - // create_marketplace(scenario); - // mint_kitty(scenario); - // list_kitty(scenario); - - // test_scenario::next_tx(scenario, SELLER); - // { - // let mkp_val = test_scenario::take_shared(scenario); - // let mkp = &mut mkp_val; - // let bag = test_scenario::take_child_object(scenario, mkp); - // let listing = test_scenario::take_child_object>>(scenario, &bag); - - // // Do the delist operation on a Marketplace. - // let nft = marketplace::delist(mkp, &mut bag, listing, test_scenario::ctx(scenario)); - // let kitty_id = burn_kitty(nft); - - // assert!(kitty_id == 1, 0); - - // test_scenario::return_shared(mkp_val); - // test_scenario::return_to_sender(scenario, bag); - // }; - // } - - // TODO(dyn-child) redo test with dynamic child object loading - // #[test] - // #[expected_failure(abort_code = 1)] - // fun fail_to_delist() { - // let scenario = &mut test_scenario::begin(ADMIN); - - // create_marketplace(scenario); - // mint_some_coin(scenario); - // mint_kitty(scenario); - // list_kitty(scenario); - - // // BUYER attempts to delist Kitty and he has no right to do so. :( - // test_scenario::next_tx(scenario, BUYER); - // { - // let mkp_val = test_scenario::take_shared(scenario); - // let mkp = &mut mkp_val; - // let bag = test_scenario::take_child_object(scenario, mkp); - // let listing = test_scenario::take_child_object>>(scenario, &bag); - - // // Do the delist operation on a Marketplace. - // let nft = marketplace::delist(mkp, &mut bag, listing, test_scenario::ctx(scenario)); - // let _ = burn_kitty(nft); - - // test_scenario::return_shared(mkp_val); - // test_scenario::return_to_sender(scenario, bag); - // }; - // } - - // TODO(dyn-child) redo test with dynamic child object loading - // #[test] - // fun buy_kitty() { - // let scenario = &mut test_scenario::begin(ADMIN); - - // create_marketplace(scenario); - // mint_some_coin(scenario); - // mint_kitty(scenario); - // list_kitty(scenario); - - // // BUYER takes 100 SUI from his wallet and purchases Kitty. - // test_scenario::next_tx(scenario, BUYER); - // { - // let coin = test_scenario::take_from_sender>(scenario); - // let mkp_val = test_scenario::take_shared(scenario); - // let mkp = &mut mkp_val; - // let bag = test_scenario::take_child_object(scenario, mkp); - // let listing = test_scenario::take_child_object>>(scenario, &bag); - // let payment = coin::take(coin::balance_mut(&mut coin), 100, test_scenario::ctx(scenario)); - - // // Do the buy call and expect successful purchase. - // let nft = marketplace::buy(&mut bag, listing, payment); - // let kitty_id = burn_kitty(nft); - - // assert!(kitty_id == 1, 0); - - // test_scenario::return_shared(mkp_val); - // test_scenario::return_to_sender(scenario, bag); - // test_scenario::return_to_sender(scenario, coin); - // }; - // } - - // TODO(dyn-child) redo test with dynamic child object loading - // #[test] - // #[expected_failure(abort_code = 0)] - // fun fail_to_buy() { - // let scenario = &mut test_scenario::begin(ADMIN); - - // create_marketplace(scenario); - // mint_some_coin(scenario); - // mint_kitty(scenario); - // list_kitty(scenario); - - // // BUYER takes 100 SUI from his wallet and purchases Kitty. - // test_scenario::next_tx(scenario, BUYER); - // { - // let coin = test_scenario::take_from_sender>(scenario); - // let mkp_val = test_scenario::take_shared(scenario); - // let mkp = &mut mkp_val; - // let bag = test_scenario::take_child_object(scenario, mkp); - // let listing = test_scenario::take_child_object>>(scenario, &bag); - - // // AMOUNT here is 10 while expected is 100. - // let payment = coin::take(coin::balance_mut(&mut coin), 10, test_scenario::ctx(scenario)); - - // // Attempt to buy and expect failure purchase. - // let nft = marketplace::buy(&mut bag, listing, payment); - // let _ = burn_kitty(nft); - - // test_scenario::return_shared(mkp_val); - // test_scenario::return_to_sender(scenario, bag); - // test_scenario::return_to_sender(scenario, coin); - // }; - // } - - #[allow(unused_function)] - fun burn_kitty(kitty: Kitty): u8 { - let Kitty{ id, kitty_id } = kitty; - object::delete(id); - kitty_id - } -} diff --git a/sui_programmability/examples/nfts/sources/num.move b/sui_programmability/examples/nfts/sources/num.move deleted file mode 100644 index 3774f3c5f0331..0000000000000 --- a/sui_programmability/examples/nfts/sources/num.move +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module nfts::num { - /// Very silly NFT: a natural number! - public struct Num has key, store { - id: UID, - n: u64 - } - - public struct NumIssuerCap has key { - id: UID, - /// Number of NFT's in circulation. Fluctuates with minting and burning. - /// A maximum of `MAX_SUPPLY` NFT's can exist at a given time. - supply: u64, - /// Total number of NFT's that have been issued. Always <= `supply`. - /// The next NFT to be issued will have the value of the counter. - issued_counter: u64, - } - - /// Only allow 10 NFT's to exist at once. Gotta make those NFT's rare! - const MAX_SUPPLY: u64 = 10; - - /// Created more than the maximum supply of Num NFT's - const ETooManyNums: u64 = 0; - - #[allow(unused_function)] - /// Create a unique issuer cap and give it to the transaction sender - fun init(ctx: &mut TxContext) { - let issuer_cap = NumIssuerCap { - id: object::new(ctx), - supply: 0, - issued_counter: 0, - }; - transfer::transfer(issuer_cap, tx_context::sender(ctx)) - } - - /// Create a new `Num` NFT. Aborts if `MAX_SUPPLY` NFT's have already been issued - public fun mint(cap: &mut NumIssuerCap, ctx: &mut TxContext): Num { - let n = cap.issued_counter; - cap.issued_counter = n + 1; - cap.supply = cap.supply + 1; - assert!(cap.supply <= MAX_SUPPLY, ETooManyNums); - Num { id: object::new(ctx), n } - } - - /// Burn `nft`. This reduces the supply. - /// Note: if we burn (e.g.) the NFT for 7, that means - /// no Num with the value 7 can exist again! But if the supply - /// is maxed out, burning will allow us to mint new Num's with - /// higher values. - public fun burn(cap: &mut NumIssuerCap, nft: Num) { - let Num { id, n: _ } = nft; - cap.supply = cap.supply - 1; - object::delete(id); - } -} diff --git a/sui_programmability/examples/nfts/sources/shared_auction.move b/sui_programmability/examples/nfts/sources/shared_auction.move deleted file mode 100644 index 57b799370c1bd..0000000000000 --- a/sui_programmability/examples/nfts/sources/shared_auction.move +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// This is an implementation of an English auction -/// (https://en.wikipedia.org/wiki/English_auction) using shared -/// objects. There are types of participants: -/// - owner - this is the original owner of an item that is sold at an -/// auction; the owner creates an auction and ends it the time of her -/// choice -/// - bidders - these are parties interested in purchasing items sold -/// at an auction; similarly to the owner they have access to the -/// auction object and can submit bids to change its state - -/// A typical lifetime of an auction looks as follows: -/// - auction is created by the owner and shared with the bidders -/// - bidders submit bids to try out-bidding one another -/// - if a submitted bid is higher than the current bid (initially -/// there is no bid), the auction is updated with the current bid -/// and funds representing previous highest bid are sent to the -/// original owner -/// - otherwise (bid is too low) the bidder's funds are sent back to -/// the bidder and the auction remains unchanged -/// - the owner eventually ends the auction -/// - if no bids were received, the item goes back to the owner -/// - otherwise the funds accumulated in the auction go to the owner -/// and the item goes to the bidder that won the auction - -module nfts::shared_auction { - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - - use nfts::auction_lib::{Self, Auction}; - - // Error codes. - - /// An attempt to end auction by a different user than the owner - const EWrongOwner: u64 = 1; - - // Entry functions. - - /// Creates an auction. This is executed by the owner of the asset - /// to be auctioned. - public entry fun create_auction(to_sell: T, ctx: &mut TxContext) { - let auction = auction_lib::create_auction(to_sell, ctx); - auction_lib::share_object(auction); - } - - /// Sends a bid to the auction. The result is either successful - /// change of the auction state (if bid was high enough) or return - /// of the funds (if the bid was too low). This is executed by a - /// bidder. - public entry fun bid( - coin: Coin, auction: &mut Auction, ctx: &mut TxContext - ) { - auction_lib::update_auction( - auction, - tx_context::sender(ctx), - coin::into_balance(coin), - ctx - ); - } - - /// Ends the auction - transfers item to the currently highest - /// bidder or back to the original owner if no bids have been - /// placed. This is executed by the owner of the asset to be - /// auctioned. - public entry fun end_auction( - auction: &mut Auction, ctx: &mut TxContext - ) { - let owner = auction_lib::auction_owner(auction); - assert!(tx_context::sender(ctx) == owner, EWrongOwner); - auction_lib::end_shared_auction(auction, ctx); - } - -} diff --git a/sui_programmability/examples/nfts/tests/auction_tests.move b/sui_programmability/examples/nfts/tests/auction_tests.move deleted file mode 100644 index 2e9b32517c2c6..0000000000000 --- a/sui_programmability/examples/nfts/tests/auction_tests.move +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module nfts::auction_tests { - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::test_scenario::Self; - - use nfts::auction::{Self, Bid}; - use nfts::auction_lib::Auction; - - // Error codes. - const EWRONG_ITEM_VALUE: u64 = 1; - - // Example of an object type that could be sold at an auction. - public struct SomeItemToSell has key, store { - id: UID, - value: u64, - } - - // Initializes the "state of the world" that mimics what should - // be available in Sui genesis state (e.g., mints and distributes - // coins to users). - fun init_bidders(ctx: &mut TxContext, mut bidders: vector
) { - while (!vector::is_empty(&bidders)) { - let bidder = vector::pop_back(&mut bidders); - let coin = coin::mint_for_testing(100, ctx); - transfer::public_transfer(coin, bidder); - }; - } - - #[test] - fun simple_auction_test() { - let auctioneer = @0xABBA; - let owner = @0xACE; - let bidder1 = @0xFACE; - let bidder2 = @0xCAFE; - - let mut scenario_val = test_scenario::begin(auctioneer); - let scenario = &mut scenario_val; - { - let mut bidders = vector::empty(); - vector::push_back(&mut bidders, bidder1); - vector::push_back(&mut bidders, bidder2); - init_bidders(test_scenario::ctx(scenario), bidders); - }; - - // a transaction by the item owner to put it for auction - test_scenario::next_tx(scenario, owner); - let ctx = test_scenario::ctx(scenario); - let to_sell = SomeItemToSell { - id: object::new(ctx), - value: 42, - }; - // create the auction - let auction_id = auction::create_auction(to_sell, auctioneer, ctx); - - // a transaction by the first bidder to create and put a bid - test_scenario::next_tx(scenario, bidder1); - { - let coin = test_scenario::take_from_sender>(scenario); - - auction::bid(coin, auction_id, auctioneer, test_scenario::ctx(scenario)); - }; - - // a transaction by the auctioneer to update state of the auction - test_scenario::next_tx(scenario, auctioneer); - { - let mut auction = test_scenario::take_from_sender>(scenario); - - let bid = test_scenario::take_from_sender(scenario); - auction::update_auction(&mut auction, bid, test_scenario::ctx(scenario)); - - test_scenario::return_to_sender(scenario, auction); - }; - // a transaction by the second bidder to create and put a bid (a - // bid will fail as it has the same value as that of the first - // bidder's) - test_scenario::next_tx(scenario, bidder2); - { - let coin = test_scenario::take_from_sender>(scenario); - - auction::bid(coin, auction_id, auctioneer, test_scenario::ctx(scenario)); - }; - - // a transaction by the auctioneer to update state of the auction - test_scenario::next_tx(scenario, auctioneer); - { - let mut auction = test_scenario::take_from_sender>(scenario); - - let bid = test_scenario::take_from_sender(scenario); - auction::update_auction(&mut auction, bid, test_scenario::ctx(scenario)); - - test_scenario::return_to_sender(scenario, auction); - }; - - // a transaction by the auctioneer to end auction - test_scenario::next_tx(scenario, auctioneer); - { - let auction = test_scenario::take_from_sender>(scenario); - - auction::end_auction(auction, test_scenario::ctx(scenario)); - }; - - // a transaction to check if the first bidder won (as the - // second bidder's bid was the same as that of the first one) - test_scenario::next_tx(scenario, bidder1); - { - let acquired_item = test_scenario::take_from_sender(scenario); - - assert!(acquired_item.value == 42, EWRONG_ITEM_VALUE); - - test_scenario::return_to_sender(scenario, acquired_item); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/nfts/tests/chat_tests.move b/sui_programmability/examples/nfts/tests/chat_tests.move deleted file mode 100644 index 86e1e9f0b7d4f..0000000000000 --- a/sui_programmability/examples/nfts/tests/chat_tests.move +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module nfts::chat_tests { - use nfts::chat::{Self, Chat}; - use std::ascii::Self; - use sui::test_scenario::Self; - - const USER1_ADDRESS: address = @0xA001; - const METADATA: vector = vector[0u8]; - const HELLO: vector = vector[72, 101, 108, 108, 111]; // "Hello" in ASCII. - - #[test] - fun test_chat() { - let mut scenario_val = test_scenario::begin(USER1_ADDRESS); - let scenario = &mut scenario_val; - { - chat::post( - @0xC001, // This should be an application object ID. - HELLO, - METADATA, // Some metadata (it could be empty). - test_scenario::ctx(scenario) - ); - }; - - test_scenario::next_tx(scenario, USER1_ADDRESS); - { - assert!(test_scenario::has_most_recent_for_sender(scenario), 0); - let chat = test_scenario::take_from_sender(scenario); // if can remove, object exists - assert!(chat::text(&chat) == ascii::string(HELLO), 0); - test_scenario::return_to_sender(scenario, chat); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/nfts/tests/cross_chain_airdrop_tests.move b/sui_programmability/examples/nfts/tests/cross_chain_airdrop_tests.move deleted file mode 100644 index 99f8ad7a0a5a3..0000000000000 --- a/sui_programmability/examples/nfts/tests/cross_chain_airdrop_tests.move +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module nfts::cross_chain_airdrop_tests { - use nfts::cross_chain_airdrop::{Self, CrossChainAirdropOracle, ERC721}; - use sui::test_scenario::{Self, Scenario}; - - // Error codes - - /// Trying to claim a token that has already been claimed - const EOBJECT_NOT_FOUND: u64 = 1; - - const ORACLE_ADDRESS: address = @0x1000; - const RECIPIENT_ADDRESS: address = @0x10; - const SOURCE_CONTRACT_ADDRESS: vector = x"BC4CA0EdA7647A8aB7C2061c2E118A18a936f13D"; - const SOURCE_TOKEN_ID: u64 = 101; - const NAME: vector = b"BoredApeYachtClub"; - const TOKEN_URI: vector = b"ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/101"; - - public struct Object has key { - id: UID, - } - - #[test] - fun test_claim_airdrop() { - let (mut scenario, oracle_address) = init_scenario(); - - // claim a token - claim_token(&mut scenario, oracle_address, SOURCE_TOKEN_ID); - - // verify that the recipient has received the nft - assert!(owns_object(RECIPIENT_ADDRESS), EOBJECT_NOT_FOUND); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = cross_chain_airdrop::ETokenIDClaimed)] - fun test_double_claim() { - let (mut scenario, oracle_address) = init_scenario(); - - // claim a token - claim_token(&mut scenario, oracle_address, SOURCE_TOKEN_ID); - - // claim the same token again - claim_token(&mut scenario, oracle_address, SOURCE_TOKEN_ID); - test_scenario::end(scenario); - } - - fun init_scenario(): (Scenario, address) { - let mut scenario = test_scenario::begin(ORACLE_ADDRESS); - { - let ctx = test_scenario::ctx(&mut scenario); - cross_chain_airdrop::test_init(ctx); - }; - (scenario, ORACLE_ADDRESS) - } - - fun claim_token(scenario: &mut Scenario, oracle_address: address, token_id: u64) { - test_scenario::next_tx(scenario, oracle_address); - { - let mut oracle = test_scenario::take_from_sender(scenario); - let ctx = test_scenario::ctx(scenario); - cross_chain_airdrop::claim( - &mut oracle, - RECIPIENT_ADDRESS, - SOURCE_CONTRACT_ADDRESS, - token_id, - NAME, - TOKEN_URI, - ctx, - ); - test_scenario::return_to_sender(scenario, oracle); - }; - test_scenario::next_tx(scenario, oracle_address); - } - - fun owns_object(owner: address): bool{ - // Verify the token has been transfer to the recipient - test_scenario::has_most_recent_for_address(owner) - } -} diff --git a/sui_programmability/examples/nfts/tests/discount_coupon_tests.move b/sui_programmability/examples/nfts/tests/discount_coupon_tests.move deleted file mode 100644 index 4611c6bebbc97..0000000000000 --- a/sui_programmability/examples/nfts/tests/discount_coupon_tests.move +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module nfts::discount_coupon_tests { - use nfts::discount_coupon::{Self, DiscountCoupon}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::test_scenario::Self; - - const ISSUER_ADDRESS: address = @0xA001; - const USER1_ADDRESS: address = @0xB001; - - // Error codes. - // const MINT_FAILED: u64 = 0; - // const TRANSFER_FAILED: u64 = 1; - - #[allow(unused_function)] - // Initializes the "state of the world" that mimics what should - // be available in Sui genesis state (e.g., mints and distributes - // coins to users). - fun init(ctx: &mut TxContext) { - let coin = coin::mint_for_testing(100, ctx); - transfer::public_transfer(coin, ISSUER_ADDRESS); - } - - #[test] - fun test_mint_then_transfer() { - let mut scenario_val = test_scenario::begin(ISSUER_ADDRESS); - let scenario = &mut scenario_val; - { - init(test_scenario::ctx(scenario)); - }; - - // Mint and transfer NFT + top up recipient's address. - test_scenario::next_tx(scenario, ISSUER_ADDRESS); - { - let coin = test_scenario::take_from_sender>(scenario); - discount_coupon::mint_and_topup(coin, 10, 1648820870, USER1_ADDRESS, test_scenario::ctx(scenario)); - }; - - test_scenario::next_tx(scenario, USER1_ADDRESS); - { - assert!( - test_scenario::has_most_recent_for_sender(scenario), - 0 - ); - let nft_coupon = test_scenario::take_from_sender(scenario); // if can remove, object exists - assert!(discount_coupon::issuer(&nft_coupon) == ISSUER_ADDRESS, 0); - test_scenario::return_to_sender(scenario, nft_coupon); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/nfts/tests/shared_auction_tests.move b/sui_programmability/examples/nfts/tests/shared_auction_tests.move deleted file mode 100644 index 4174b024282ff..0000000000000 --- a/sui_programmability/examples/nfts/tests/shared_auction_tests.move +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module nfts::shared_auction_tests { - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::test_scenario; - - use nfts::shared_auction; - use nfts::auction_lib::Auction; - - - const COIN_VALUE: u64 = 100; - - // Error codes. - const EWRONG_ITEM_VALUE: u64 = 1; - const EWRONG_COIN_VALUE: u64 = 2; - - // Example of an object type that could be sold at an auction. - public struct SomeItemToSell has key, store { - id: UID, - value: u64, - } - - // Initializes the "state of the world" that mimics what should - // be available in Sui genesis state (e.g., mints and distributes - // coins to users). - fun init_bidders(ctx: &mut TxContext, mut bidders: vector
) { - while (!vector::is_empty(&bidders)) { - let bidder = vector::pop_back(&mut bidders); - let coin = coin::mint_for_testing(COIN_VALUE, ctx); - transfer::public_transfer(coin, bidder); - }; - } - - #[test] - fun simple_auction_test() { - let admin = @0xABBA; // needed only to initialize "state of the world" - let owner = @0xACE; - let bidder1 = @0xFACE; - let bidder2 = @0xCAFE; - - let mut scenario_val = test_scenario::begin(admin); - let scenario = &mut scenario_val; - { - let mut bidders = vector::empty(); - vector::push_back(&mut bidders, bidder1); - vector::push_back(&mut bidders, bidder2); - init_bidders(test_scenario::ctx(scenario), bidders); - }; - - // a transaction by the item owner to put it for auction - test_scenario::next_tx(scenario, owner); - let ctx = test_scenario::ctx(scenario); - { - let to_sell = SomeItemToSell { - id: object::new(ctx), - value: 42, - }; - shared_auction::create_auction(to_sell, ctx); - }; - - // a transaction by the first bidder to put a bid - test_scenario::next_tx(scenario, bidder1); - { - let coin = test_scenario::take_from_sender>(scenario); - let mut auction_val = test_scenario::take_shared>(scenario); - let auction = &mut auction_val; - - shared_auction::bid(coin, auction, test_scenario::ctx(scenario)); - - test_scenario::return_shared(auction_val); - }; - - // a transaction by the second bidder to put a bid (a bid will - // fail as it has the same value as that of the first - // bidder's) - test_scenario::next_tx(scenario, bidder2); - { - let coin = test_scenario::take_from_sender>(scenario); - let mut auction_val = test_scenario::take_shared>(scenario); - let auction = &mut auction_val; - - shared_auction::bid(coin, auction, test_scenario::ctx(scenario)); - - test_scenario::return_shared(auction_val); - }; - - // a transaction by the second bidder to verify that the funds - // have been returned (as a result of the failed bid). - test_scenario::next_tx(scenario, bidder2); - { - let coin = test_scenario::take_from_sender>(scenario); - - assert!(coin::value(&coin) == COIN_VALUE, EWRONG_COIN_VALUE); - - test_scenario::return_to_sender(scenario, coin); - }; - - // a transaction by the owner to end auction - test_scenario::next_tx(scenario, owner); - { - let mut auction_val = test_scenario::take_shared>(scenario); - let auction = &mut auction_val; - - // pass auction as mutable reference as its a shared - // object that cannot be deleted - shared_auction::end_auction(auction, test_scenario::ctx(scenario)); - - test_scenario::return_shared(auction_val); - }; - - // a transaction to check if the first bidder won (as the - // second bidder's bid was the same as that of the first one) - test_scenario::next_tx(scenario, bidder1); - { - let acquired_item = test_scenario::take_from_sender(scenario); - - assert!(acquired_item.value == 42, EWRONG_ITEM_VALUE); - - test_scenario::return_to_sender(scenario, acquired_item); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/objects_tutorial/Move.toml b/sui_programmability/examples/objects_tutorial/Move.toml deleted file mode 100644 index 53864a96315f3..0000000000000 --- a/sui_programmability/examples/objects_tutorial/Move.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "Tutorial" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -MoveStdlib = { local = "../../../crates/sui-framework/packages/move-stdlib/" } -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -tutorial = "0x0" diff --git a/sui_programmability/examples/objects_tutorial/sources/color_object.move b/sui_programmability/examples/objects_tutorial/sources/color_object.move deleted file mode 100644 index 844d4778d3820..0000000000000 --- a/sui_programmability/examples/objects_tutorial/sources/color_object.move +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module tutorial::color_object { - public struct ColorObject has key { - id: UID, - red: u8, - green: u8, - blue: u8, - } - - // == Functions covered in Chapter 1 == - - fun new(red: u8, green: u8, blue: u8, ctx: &mut TxContext): ColorObject { - ColorObject { - id: object::new(ctx), - red, - green, - blue, - } - } - - public entry fun create(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { - let color_object = new(red, green, blue, ctx); - transfer::transfer(color_object, tx_context::sender(ctx)) - } - - public fun get_color(self: &ColorObject): (u8, u8, u8) { - (self.red, self.green, self.blue) - } - - // == Functions covered in Chapter 2 == - - /// Copies the values of `from_object` into `into_object`. - public entry fun copy_into(from_object: &ColorObject, into_object: &mut ColorObject) { - into_object.red = from_object.red; - into_object.green = from_object.green; - into_object.blue = from_object.blue; - } - - public entry fun delete(object: ColorObject) { - let ColorObject { id, red: _, green: _, blue: _ } = object; - object::delete(id); - } - - public entry fun transfer(object: ColorObject, recipient: address) { - transfer::transfer(object, recipient) - } - - // == Functions covered in Chapter 3 == - - public entry fun freeze_object(object: ColorObject) { - transfer::freeze_object(object) - } - - public entry fun create_immutable(red: u8, green: u8, blue: u8, ctx: &mut TxContext) { - let color_object = new(red, green, blue, ctx); - transfer::freeze_object(color_object) - } - - public entry fun update( - object: &mut ColorObject, - red: u8, green: u8, blue: u8, - ) { - object.red = red; - object.green = green; - object.blue = blue; - } -} - -#[test_only] -module tutorial::color_object_tests { - use sui::test_scenario; - use tutorial::color_object::{Self, ColorObject}; - - // == Tests covered in Chapter 1 == - - #[test] - fun test_create() { - let owner = @0x1; - // Create a ColorObject and transfer it to @owner. - let mut scenario_val = test_scenario::begin(owner); - let scenario = &mut scenario_val; - { - let ctx = test_scenario::ctx(scenario); - color_object::create(255, 0, 255, ctx); - }; - // Check that @not_owner does not own the just-created ColorObject. - let not_owner = @0x2; - test_scenario::next_tx(scenario, not_owner); - { - assert!(!test_scenario::has_most_recent_for_sender(scenario), 0); - }; - // Check that @owner indeed owns the just-created ColorObject. - // Also checks the value fields of the object. - test_scenario::next_tx(scenario, owner); - { - let object = test_scenario::take_from_sender(scenario); - let (red, green, blue) = color_object::get_color(&object); - assert!(red == 255 && green == 0 && blue == 255, 0); - test_scenario::return_to_sender(scenario, object); - }; - test_scenario::end(scenario_val); - } - - // == Tests covered in Chapter 2 == - - #[test] - fun test_copy_into() { - let owner = @0x1; - let mut scenario_val = test_scenario::begin(owner); - let scenario = &mut scenario_val; - // Create two ColorObjects owned by `owner`, and obtain their IDs. - let (id1, id2) = { - let ctx = test_scenario::ctx(scenario); - color_object::create(255, 255, 255, ctx); - let id1 = - object::id_from_address(tx_context::last_created_object_id(ctx)); - color_object::create(0, 0, 0, ctx); - let id2 = - object::id_from_address(tx_context::last_created_object_id(ctx)); - (id1, id2) - }; - test_scenario::next_tx(scenario, owner); - { - let mut obj1 = test_scenario::take_from_sender_by_id(scenario, id1); - let obj2 = test_scenario::take_from_sender_by_id(scenario, id2); - let (red, green, blue) = color_object::get_color(&obj1); - assert!(red == 255 && green == 255 && blue == 255, 0); - - color_object::copy_into(&obj2, &mut obj1); - test_scenario::return_to_sender(scenario, obj1); - test_scenario::return_to_sender(scenario, obj2); - }; - test_scenario::next_tx(scenario, owner); - { - let obj1 = test_scenario::take_from_sender_by_id(scenario, id1); - let (red, green, blue) = color_object::get_color(&obj1); - assert!(red == 0 && green == 0 && blue == 0, 0); - test_scenario::return_to_sender(scenario, obj1); - }; - test_scenario::end(scenario_val); - } - - #[test] - fun test_delete() { - let owner = @0x1; - // Create a ColorObject and transfer it to @owner. - let mut scenario_val = test_scenario::begin(owner); - let scenario = &mut scenario_val; - { - let ctx = test_scenario::ctx(scenario); - color_object::create(255, 0, 255, ctx); - }; - // Delete the ColorObject we just created. - test_scenario::next_tx(scenario, owner); - { - let object = test_scenario::take_from_sender(scenario); - color_object::delete(object); - }; - // Verify that the object was indeed deleted. - test_scenario::next_tx(scenario, owner); - { - assert!(!test_scenario::has_most_recent_for_sender(scenario), 0); - }; - test_scenario::end(scenario_val); - } - - #[test] - fun test_transfer() { - let owner = @0x1; - // Create a ColorObject and transfer it to @owner. - let mut scenario_val = test_scenario::begin(owner); - let scenario = &mut scenario_val; - { - let ctx = test_scenario::ctx(scenario); - color_object::create(255, 0, 255, ctx); - }; - // Transfer the object to recipient. - let recipient = @0x2; - test_scenario::next_tx(scenario, owner); - { - let object = test_scenario::take_from_sender(scenario); - color_object::transfer(object, recipient); - }; - // Check that owner no longer owns the object. - test_scenario::next_tx(scenario, owner); - { - assert!(!test_scenario::has_most_recent_for_sender(scenario), 0); - }; - // Check that recipient now owns the object. - test_scenario::next_tx(scenario, recipient); - { - assert!(test_scenario::has_most_recent_for_sender(scenario), 0); - }; - test_scenario::end(scenario_val); - } - - // == Tests covered in Chapter 3 == - - #[test] - fun test_immutable() { - let sender1 = @0x1; - let mut scenario_val = test_scenario::begin(sender1); - let scenario = &mut scenario_val; - { - let ctx = test_scenario::ctx(scenario); - color_object::create_immutable(255, 0, 255, ctx); - }; - test_scenario::next_tx(scenario, sender1); - { - // take_owned does not work for immutable objects. - assert!(!test_scenario::has_most_recent_for_sender(scenario), 0); - }; - // Any sender can work. - let sender2 = @0x2; - test_scenario::next_tx(scenario, sender2); - { - let object_val = test_scenario::take_immutable(scenario); - let object = &object_val; - let (red, green, blue) = color_object::get_color(object); - assert!(red == 255 && green == 0 && blue == 255, 0); - test_scenario::return_immutable(object_val); - }; - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/objects_tutorial/sources/simple_warrior.move b/sui_programmability/examples/objects_tutorial/sources/simple_warrior.move deleted file mode 100644 index 2e5e926927799..0000000000000 --- a/sui_programmability/examples/objects_tutorial/sources/simple_warrior.move +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module tutorial::simple_warrior { - public struct Sword has key, store { - id: UID, - strength: u8, - } - - public struct Shield has key, store { - id: UID, - armor: u8, - } - - public struct SimpleWarrior has key { - id: UID, - sword: Option, - shield: Option, - } - - public entry fun create_sword(strength: u8, ctx: &mut TxContext) { - let sword = Sword { - id: object::new(ctx), - strength, - }; - transfer::transfer(sword, tx_context::sender(ctx)) - } - - public entry fun create_shield(armor: u8, ctx: &mut TxContext) { - let shield = Shield { - id: object::new(ctx), - armor, - }; - transfer::transfer(shield, tx_context::sender(ctx)) - } - - public entry fun create_warrior(ctx: &mut TxContext) { - let warrior = SimpleWarrior { - id: object::new(ctx), - sword: option::none(), - shield: option::none(), - }; - transfer::transfer(warrior, tx_context::sender(ctx)) - } - - public entry fun equip_sword(warrior: &mut SimpleWarrior, sword: Sword, ctx: &TxContext) { - if (option::is_some(&warrior.sword)) { - let old_sword = option::extract(&mut warrior.sword); - transfer::transfer(old_sword, tx_context::sender(ctx)); - }; - option::fill(&mut warrior.sword, sword); - } - - public entry fun equip_shield(warrior: &mut SimpleWarrior, shield: Shield, ctx: &TxContext) { - if (option::is_some(&warrior.shield)) { - let old_shield = option::extract(&mut warrior.shield); - transfer::transfer(old_shield, tx_context::sender(ctx)); - }; - option::fill(&mut warrior.shield, shield); - } -} diff --git a/sui_programmability/examples/objects_tutorial/sources/trusted_swap.move b/sui_programmability/examples/objects_tutorial/sources/trusted_swap.move deleted file mode 100644 index de54fac5c95f2..0000000000000 --- a/sui_programmability/examples/objects_tutorial/sources/trusted_swap.move +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module tutorial::trusted_swap { - use sui::balance::{Self, Balance}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - - const MIN_FEE: u64 = 1000; - - public struct Object has key, store { - id: UID, - scarcity: u8, - style: u8, - } - - public struct ObjectWrapper has key { - id: UID, - original_owner: address, - to_swap: Object, - fee: Balance, - } - - public entry fun create_object(scarcity: u8, style: u8, ctx: &mut TxContext) { - let object = Object { - id: object::new(ctx), - scarcity, - style, - }; - transfer::public_transfer(object, tx_context::sender(ctx)) - } - - /// Anyone owns an `Object` can request swapping their object. This object - /// will be wrapped into `ObjectWrapper` and sent to `service_address`. - public entry fun request_swap(object: Object, fee: Coin, service_address: address, ctx: &mut TxContext) { - assert!(coin::value(&fee) >= MIN_FEE, 0); - let wrapper = ObjectWrapper { - id: object::new(ctx), - original_owner: tx_context::sender(ctx), - to_swap: object, - fee: coin::into_balance(fee), - }; - transfer::transfer(wrapper, service_address); - } - - /// When the admin has two swap requests with objects that are trade-able, - /// the admin can execute the swap and send them back to the opposite owner. - public entry fun execute_swap(wrapper1: ObjectWrapper, wrapper2: ObjectWrapper, ctx: &mut TxContext) { - // Only swap if their scarcity is the same and style is different. - assert!(wrapper1.to_swap.scarcity == wrapper2.to_swap.scarcity, 0); - assert!(wrapper1.to_swap.style != wrapper2.to_swap.style, 0); - - // Unpack both wrappers, cross send them to the other owner. - let ObjectWrapper { - id: id1, - original_owner: original_owner1, - to_swap: object1, - fee: mut fee1, - } = wrapper1; - - let ObjectWrapper { - id: id2, - original_owner: original_owner2, - to_swap: object2, - fee: fee2, - } = wrapper2; - - // Perform the swap. - transfer::transfer(object1, original_owner2); - transfer::transfer(object2, original_owner1); - - // Service provider takes the fee. - let service_address = tx_context::sender(ctx); - balance::join(&mut fee1, fee2); - transfer::public_transfer(coin::from_balance(fee1, ctx), service_address); - - // Effectively delete the wrapper objects. - object::delete(id1); - object::delete(id2); - } -} diff --git a/sui_programmability/examples/utils/Move.toml b/sui_programmability/examples/utils/Move.toml deleted file mode 100644 index 0c6408ee6a336..0000000000000 --- a/sui_programmability/examples/utils/Move.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "utils" -version = "0.0.1" -edition = "2024.beta" - -[dependencies] -Sui = { local = "../../../crates/sui-framework/packages/sui-framework" } - -[addresses] -utils = "0x0" diff --git a/sui_programmability/examples/utils/sources/epoch_time_lock.move b/sui_programmability/examples/utils/sources/epoch_time_lock.move deleted file mode 100644 index b6820e8b74210..0000000000000 --- a/sui_programmability/examples/utils/sources/epoch_time_lock.move +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module utils::epoch_time_lock { - - /// The epoch passed into the creation of a lock has already passed. - const EEpochAlreadyPassed: u64 = 0; - - /// Attempt is made to unlock a lock that cannot be unlocked yet. - const EEpochNotYetEnded: u64 = 1; - - /// Holder of an epoch number that can only be discarded in the epoch or - /// after the epoch has passed. - public struct EpochTimeLock has store, copy { - epoch: u64 - } - - /// Create a new epoch time lock with `epoch`. Aborts if the current epoch is less than the input epoch. - public fun new(epoch: u64, ctx: &TxContext) : EpochTimeLock { - assert!(ctx.epoch() < epoch, EEpochAlreadyPassed); - EpochTimeLock { epoch } - } - - /// Destroys an epoch time lock. Aborts if the current epoch is less than the locked epoch. - public fun destroy(lock: EpochTimeLock, ctx: &TxContext) { - let EpochTimeLock { epoch } = lock; - assert!(ctx.epoch() >= epoch, EEpochNotYetEnded); - } - - /// Getter for the epoch number. - public fun epoch(lock: &EpochTimeLock): u64 { - lock.epoch - } -} diff --git a/sui_programmability/examples/utils/sources/immutable_external_resource.move b/sui_programmability/examples/utils/sources/immutable_external_resource.move deleted file mode 100644 index 6853765d993d1..0000000000000 --- a/sui_programmability/examples/utils/sources/immutable_external_resource.move +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Sui types for specifying off-chain/external resources. -/// -/// The keywords "MUST", "MUST NOT", "SHOULD", "SHOULD NOT" and "MAY" below should be interpreted as described in -/// RFC 2119. -/// -module utils::immutable_external_resource { - use sui::url::{Url, inner_url}; - - /// ImmutableExternalResource: An arbitrary, mutable URL plus an immutable digest of the resource. - /// - /// Represents a resource that can move but must never change. Example use cases: - /// - NFT images. - /// - NFT metadata. - /// - /// `url` MUST follow RFC-3986. Clients MUST support (at least) the following schemes: ipfs, https. - /// `digest` MUST be set to SHA3-256(content of resource at `url`). - /// - /// Clients of this type MUST fetch the resource at `url`, compute its digest and compare it against `digest`. If - /// the result is false, clients SHOULD indicate that to users or ignore the resource. - public struct ImmutableExternalResource has store, copy, drop { - url: Url, - digest: vector, - } - - /// Create a `ImmutableExternalResource`, and set the immutable hash. - public fun new(url: Url, digest: vector): ImmutableExternalResource { - ImmutableExternalResource { url, digest } - } - - /// Get the hash of the resource. - public fun digest(self: &ImmutableExternalResource): vector { - self.digest - } - - /// Get the URL of the resource. - public fun url(self: &ImmutableExternalResource): Url { - self.url - } - - /// Update the URL, but the digest of the resource must never change. - public fun update(self: &mut ImmutableExternalResource, url: Url) { - self.url.update(url.inner_url()) - } -} diff --git a/sui_programmability/examples/utils/sources/locked_coin.move b/sui_programmability/examples/utils/sources/locked_coin.move deleted file mode 100644 index 4e72dc03543a5..0000000000000 --- a/sui_programmability/examples/utils/sources/locked_coin.move +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -module utils::locked_coin { - use sui::balance::Balance; - use sui::coin::{Self, Coin}; - use utils::epoch_time_lock::{Self, EpochTimeLock}; - - /// A coin of type `T` locked until `locked_until_epoch`. - public struct LockedCoin has key { - id: UID, - balance: Balance, - locked_until_epoch: EpochTimeLock - } - - /// Create a LockedCoin from `balance` and transfer it to `owner`. - public fun new_from_balance(balance: Balance, locked_until_epoch: EpochTimeLock, owner: address, ctx: &mut TxContext) { - let locked_coin = LockedCoin { - id: object::new(ctx), - balance, - locked_until_epoch - }; - transfer::transfer(locked_coin, owner); - } - - /// Public getter for the locked coin's value - public fun value(self: &LockedCoin): u64 { - self.balance.value() - } - - /// Lock a coin up until `locked_until_epoch`. The input Coin is deleted and a LockedCoin - /// is transferred to the `recipient`. This function aborts if the `locked_until_epoch` is less than - /// or equal to the current epoch. - public entry fun lock_coin( - coin: Coin, recipient: address, locked_until_epoch: u64, ctx: &mut TxContext - ) { - let balance = coin.into_balance(); - new_from_balance(balance, epoch_time_lock::new(locked_until_epoch, ctx), recipient, ctx); - } - - /// Unlock a locked coin. The function aborts if the current epoch is less than the `locked_until_epoch` - /// of the coin. If the check is successful, the locked coin is deleted and a Coin is transferred back - /// to the sender. - public entry fun unlock_coin(locked_coin: LockedCoin, ctx: &mut TxContext) { - let LockedCoin { id, balance, locked_until_epoch } = locked_coin; - id.delete(); - locked_until_epoch.destroy(ctx); - let coin = coin::from_balance(balance, ctx); - transfer::public_transfer(coin, ctx.sender()); - } -} diff --git a/sui_programmability/examples/utils/sources/safe.move b/sui_programmability/examples/utils/sources/safe.move deleted file mode 100644 index 12eed36871463..0000000000000 --- a/sui_programmability/examples/utils/sources/safe.move +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// The Safe standard is a minimalistic shared wrapper around a coin. It provides a way for users to provide third-party dApps with -/// the capability to transfer coins away from their wallets, if they are provided with the correct permission. -module utils::safe { - use sui::balance::{Self, Balance}; - use sui::coin::{Self, Coin}; - use sui::vec_set::{Self, VecSet}; - - // Errors - const EInvalidTransferCapability: u64 = 0; - const EInvalidOwnerCapability: u64 = 1; - const ETransferCapabilityRevoked: u64 = 2; - const EOverdrawn: u64 = 3; - - // - /// Allows any holder of a capability to transfer a fixed amount of assets from the safe. - /// Useful in situations like an NFT marketplace where you wish to buy the NFTs at a specific price. - /// - /// @ownership: Shared - /// - public struct Safe has key { - id: UID, - balance: Balance, - allowed_safes: VecSet, - } - - public struct OwnerCapability has key, store { - id: UID, - safe_id: ID, - } - - /// - /// Allows the owner of the capability to take `amount` of coins from the box. - /// - /// @ownership: Owned - /// - public struct TransferCapability has store, key { - id: UID, - safe_id: ID, - // The amount that the user is able to transfer. - amount: u64, - } - - ////////////////////////////////////////////////////// - /// HELPER FUNCTIONS - ////////////////////////////////////////////////////// - - /// Check that the capability has not yet been revoked by the owner. - fun check_capability_validity(safe: &Safe, capability: &TransferCapability) { - // Check that the ids match - assert!(object::id(safe) == capability.safe_id, EInvalidTransferCapability); - // Check that it has not been cancelled - assert!(safe.allowed_safes.contains(&object::id(capability)), ETransferCapabilityRevoked); - } - - fun check_owner_capability_validity(safe: &Safe, capability: &OwnerCapability) { - assert!(object::id(safe) == capability.safe_id, EInvalidOwnerCapability); - } - - /// Helper function to create a capability. - fun create_capability_(safe: &mut Safe, withdraw_amount: u64, ctx: &mut TxContext): TransferCapability { - let cap_id = object::new(ctx); - safe.allowed_safes.insert(cap_id.uid_to_inner()); - - let capability = TransferCapability { - id: cap_id, - safe_id: safe.id.uid_to_inner(), - amount: withdraw_amount, - }; - - capability - } - - ////////////////////////////////////////////////////// - /// PUBLIC FUNCTIONS - ////////////////////////////////////////////////////// - - public fun balance(safe: &Safe): &Balance { - &safe.balance - } - - /// Wrap a coin around a safe. - /// a trusted party (or smart contract) to transfer the object out. - public fun create_(balance: Balance, ctx: &mut TxContext): OwnerCapability { - let safe = Safe { - id: object::new(ctx), - balance, - allowed_safes: vec_set::empty(), - }; - let cap = OwnerCapability { - id: object::new(ctx), - safe_id: object::id(&safe), - }; - transfer::share_object(safe); - cap - } - - public entry fun create(coin: Coin, ctx: &mut TxContext) { - let balance = coin.into_balance(); - let cap = create_(balance, ctx); - transfer::public_transfer(cap, ctx.sender()); - } - - public entry fun create_empty(ctx: &mut TxContext) { - let empty_balance = balance::zero(); - let cap = create_(empty_balance, ctx); - transfer::public_transfer(cap, ctx.sender()); - } - - /// Deposit funds to the safe - public fun deposit_(safe: &mut Safe, balance: Balance) { - safe.balance.join(balance); - } - - /// Deposit funds to the safe - public entry fun deposit(safe: &mut Safe, coin: Coin) { - let balance = coin.into_balance(); - deposit_(safe, balance); - } - - /// Withdraw coins from the safe as a `OwnerCapability` holder - public fun withdraw_(safe: &mut Safe, capability: &OwnerCapability, withdraw_amount: u64): Balance { - // Ensures that only the owner can withdraw from the safe. - check_owner_capability_validity(safe, capability); - safe.balance.split(withdraw_amount) - } - - /// Withdraw coins from the safe as a `OwnerCapability` holder - public entry fun withdraw(safe: &mut Safe, capability: &OwnerCapability, withdraw_amount: u64, ctx: &mut TxContext) { - let balance = withdraw_(safe, capability, withdraw_amount); - let coin = coin::from_balance(balance, ctx); - transfer::public_transfer(coin, ctx.sender()); - } - - /// Withdraw coins from the safe as a `TransferCapability` holder. - public fun debit(safe: &mut Safe, capability: &mut TransferCapability, withdraw_amount: u64): Balance { - // Check the validity of the capability - check_capability_validity(safe, capability); - - // Withdraw funds - assert!(capability.amount >= withdraw_amount, EOverdrawn); - capability.amount = capability.amount - withdraw_amount; - safe.balance.split(withdraw_amount) - } - - /// Revoke a `TransferCapability` as an `OwnerCapability` holder - public entry fun revoke_transfer_capability(safe: &mut Safe, capability: &OwnerCapability, capability_id: ID) { - // Ensures that only the owner can withdraw from the safe. - check_owner_capability_validity(safe, capability); - safe.allowed_safes.remove(&capability_id); - } - - /// Revoke a `TransferCapability` as its owner - public entry fun self_revoke_transfer_capability(safe: &mut Safe, capability: &TransferCapability) { - check_capability_validity(safe, capability); - safe.allowed_safes.remove(&object::id(capability)); - } - - /// Create `TransferCapability` as an `OwnerCapability` holder - public fun create_transfer_capability(safe: &mut Safe, capability: &OwnerCapability, withdraw_amount: u64, ctx: &mut TxContext): TransferCapability { - // Ensures that only the owner can withdraw from the safe. - check_owner_capability_validity(safe, capability); - create_capability_(safe, withdraw_amount, ctx) - } -} diff --git a/sui_programmability/examples/utils/sources/typed_id.move b/sui_programmability/examples/utils/sources/typed_id.move deleted file mode 100644 index a6759b62830ce..0000000000000 --- a/sui_programmability/examples/utils/sources/typed_id.move +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -/// Typed wrappers around Sui object IDs -/// While not always necessary, this is helpful for indicating the type of an object, particularly -/// when storing its ID in another object. -/// Additionally, it can be helpful for disambiguating between different IDs in an object. -/// For example -/// ``` -/// struct MyObject has key { -/// id: VersionedID, -/// child1: TypedID, -/// child2: TypedID, -/// } -/// ``` -/// We then know that `child1` is an ID for an object of type `A` and that `child2` is an `ID` -/// of an object of type `B` -module utils::typed_id { - /// An ID of an of type `T`. See `ID` for more details - /// By construction, it is guaranteed that the `ID` represents an object of type `T` - public struct TypedID has copy, drop, store { - id: ID, - } - - /// Get the underlying `ID` of `obj`, and remember the type - public fun new(obj: &T): TypedID { - TypedID { id: object::id(obj) } - } - - /// Borrow the inner `ID` of `typed_id` - public fun as_id(typed_id: &TypedID): &ID { - &typed_id.id - } - - /// Get the inner `ID` of `typed_id` - public fun to_id(typed_id: TypedID): ID { - let TypedID { id } = typed_id; - id - } - - /// Check that underlying `ID` in the `typed_id` equals the objects ID - public fun equals_object(typed_id: &TypedID, obj: &T): bool { - typed_id.id == object::id(obj) - } -} diff --git a/sui_programmability/examples/utils/tests/immutable_external_resource_tests.move b/sui_programmability/examples/utils/tests/immutable_external_resource_tests.move deleted file mode 100644 index 14c8246ac4a4d..0000000000000 --- a/sui_programmability/examples/utils/tests/immutable_external_resource_tests.move +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module utils::immutable_external_resource_tests { - use utils::immutable_external_resource; - use sui::url; - use std::ascii::Self; - use std::hash::sha3_256; - - const EHashStringMisMatch: u64 = 0; - const EUrlStringMisMatch: u64 = 1; - - #[test] - fun test_init() { - // url strings are not currently validated - let url_str = ascii::string(x"414243454647"); - // 32 bytes - let hash = x"1234567890123456789012345678901234567890abcdefabcdefabcdefabcdef"; - - let url = url::new_unsafe(url_str); - let digest = sha3_256(hash); - let mut resource = immutable_external_resource::new(url, digest); - - assert!(resource.url() == url, EUrlStringMisMatch); - assert!(resource.digest() == digest, EHashStringMisMatch); - - let new_url_str = ascii::string(x"37414243454647"); - let new_url = url::new_unsafe(new_url_str); - - resource.update(new_url); - assert!(resource.url() == new_url, EUrlStringMisMatch); - } -} diff --git a/sui_programmability/examples/utils/tests/safe_tests.move b/sui_programmability/examples/utils/tests/safe_tests.move deleted file mode 100644 index 399b24e294687..0000000000000 --- a/sui_programmability/examples/utils/tests/safe_tests.move +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module utils::safe_tests { - use utils::safe::{Self, Safe, TransferCapability, OwnerCapability}; - use sui::test_scenario::{Self as ts, Scenario, ctx}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::test_utils; - - const TEST_SENDER_ADDR: address = @0x1; - const TEST_OWNER_ADDR: address = @0x1337; - const TEST_DELEGATEE_ADDR: address = @0x1ce1ce1ce; - - fun create_safe(scenario: &mut Scenario, owner: address, stored_amount: u64) { - scenario.next_tx(owner); - { - let coin = coin::mint_for_testing(stored_amount, ctx(scenario)); - safe::create(coin, ctx(scenario)); - }; - } - - // Delegates the safe to delegatee and return the capability ID. - fun delegate_safe(scenario: &mut Scenario, owner: address, delegate_to: address, delegate_amount: u64): ID { - let id; - scenario.next_tx(owner); - let mut safe = scenario.take_shared>(); - let cap = scenario.take_from_sender>(); - let capability = safe.create_transfer_capability(&cap, delegate_amount, ctx(scenario)); - id = object::id(&capability); - transfer::public_transfer(capability, delegate_to); - scenario.return_to_sender(cap); - ts::return_shared(safe); - id - } - - fun withdraw_as_delegatee(scenario: &mut Scenario, delegatee: address, withdraw_amount: u64) { - scenario.next_tx(delegatee); - let mut safe = scenario.take_shared>(); - let mut capability = scenario.take_from_sender>(); - let balance = safe.debit(&mut capability, withdraw_amount); - test_utils::destroy(balance); - - scenario.return_to_sender(capability); - ts::return_shared(safe); - } - - fun revoke_capability(scenario: &mut Scenario, owner: address, capability_id: ID) { - scenario.next_tx(owner); - let mut safe = scenario.take_shared>(); - let cap = scenario.take_from_sender>(); - safe.revoke_transfer_capability(&cap, capability_id); - - scenario.return_to_sender(cap); - ts::return_shared(safe); - } - - #[test] - /// Ensure that all funds can be withdrawn by the owners - fun test_safe_create_and_withdraw_funds_as_owner() { - let owner = TEST_OWNER_ADDR; - let mut scenario = ts::begin(TEST_SENDER_ADDR); - - let initial_funds = 1000u64; - create_safe(scenario, owner, initial_funds); - - scenario.next_tx(owner); - let mut safe = scenario.take_shared>(); - let cap = scenario.take_from_sender>(); - - safe.withdraw(&cap, initial_funds, ts::ctx(scenario)); - scenario.next_tx(owner); - let withdrawn_coin = scenario.take_from_sender>(); - assert!(withdrawn_coin.value() == initial_funds, 0); - - test_utils::destroy(withdrawn_coin); - scenario.return_to_sender(cap); - ts::return_shared(safe); - - scenario.end(); - } - - #[test] - /// Ensure that all funds can be withdrawn to a delegator - fun test_safe_create_and_withdraw_funds_as_delegatee() { - let owner = TEST_OWNER_ADDR; - let delegatee = TEST_DELEGATEE_ADDR; - let mut scenario = ts::begin(TEST_SENDER_ADDR); - - let initial_funds = 1000u64; - let delegated_funds = 1000u64; - // Create Safe - create_safe(scenario, owner, initial_funds); - delegate_safe(scenario, owner, delegatee, delegated_funds); - withdraw_as_delegatee(scenario, delegatee, delegated_funds); - - scenario.end(); - } - - #[test] - #[expected_failure(abort_code = safe::EOverdrawn)] - /// Ensure that funds cannot be over withdrawn - fun test_safe_attempt_to_over_withdraw() { - let owner = TEST_OWNER_ADDR; - let delegatee = TEST_DELEGATEE_ADDR; - let mut scenario = ts::begin(TEST_SENDER_ADDR); - - let initial_funds = 1000u64; - let delegated_funds = 1000u64; - // Create Safe - create_safe(scenario, owner, initial_funds); - delegate_safe(scenario, owner, delegatee, delegated_funds); - - // Withdraw all funds - withdraw_as_delegatee(scenario, delegatee, delegated_funds); - // Attempt to withdraw by 1 coin. - withdraw_as_delegatee(scenario, delegatee, 1); - - scenario.end(); - } - - #[test] - #[expected_failure(abort_code = safe::ETransferCapabilityRevoked)] - /// Ensure that funds cannot be over withdrawn - fun test_safe_withdraw_revoked() { - let owner = TEST_OWNER_ADDR; - let delegatee = TEST_DELEGATEE_ADDR; - let mut scenario = ts::begin(TEST_SENDER_ADDR); - - let initial_funds = 1000u64; - let delegated_funds = 1000u64; - // Create Safe - create_safe(scenario, owner, initial_funds); - let capability_id = delegate_safe(scenario, owner, delegatee, delegated_funds); - - revoke_capability(scenario, owner, capability_id); - - // Withdraw funds - withdraw_as_delegatee(scenario, delegatee, delegated_funds); - - scenario.end(); - } - - #[test] - #[expected_failure(abort_code = safe::ETransferCapabilityRevoked)] - /// Ensure owner cannot withdraw funds after revoking itself. - fun test_safe_withdraw_self_revoked() { - let owner = TEST_OWNER_ADDR; - let mut scenario = ts::begin(owner); - - let initial_funds = 1000u64; - create_safe(scenario, owner, initial_funds); - - scenario.next_tx(owner); - let cap = scenario.take_from_sender>(); - let mut safe = scenario.take_shared>(); - let mut transfer_capability = safe.create_transfer_capability(&cap, initial_funds, ctx(scenario)); - // Function under test - safe.self_revoke_transfer_capability(&transfer_capability); - ts::return_shared(safe); - - // Try withdraw funds with transfer capability. - scenario.next_tx(owner); - let mut safe = scenario.take_shared>(); - let balance = safe.debit(&mut transfer_capability, 1000u64); - test_utils::destroy(balance); - - ts::return_shared(safe); - scenario.return_to_sender(cap); - scenario.return_to_sender(transfer_capability); - scenario.end(); - } -}