Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[move] new fungible token example: synthetic currency backed by baske… #1221

Merged
merged 2 commits into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sui_programmability/examples/fungible_tokens/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Fungible Tokens

* MANAGED: a token managed by a treasurer trusted for minting and burning. This is how (e.g.) a fiat-backed stablecoin would work.
* BASKET: a synthetic token backed by a basket of other assets. This how (e.g.) a [SDR](https://www.imf.org/en/About/Factsheets/Sheets/2016/08/01/14/51/Special-Drawing-Right-SDR)-like asset would work.
* FIXED: a token with a fixed supply (coming soon).
* ALGO: a token with an algorithmic issuance policy (coming soon).
92 changes: 92 additions & 0 deletions sui_programmability/examples/fungible_tokens/sources/BASKET.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) 2022, 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 FungibleTokens::BASKET {
use FungibleTokens::MANAGED::MANAGED;
use Sui::Coin::{Self, Coin, TreasuryCap};
use Sui::ID::VersionedID;
use Sui::SUI::SUI;
use Sui::Transfer;
use Sui::TxContext::{Self, TxContext};

/// 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<BASKET>`.
struct BASKET has drop { }

/// Singleton shared object holding the reserve assets and the capability.
struct Reserve has key {
id: VersionedID,
/// capability allowing the reserve to mint and burn BASKET
treasury_cap: TreasuryCap<BASKET>,
/// SUI coins held in the reserve
sui: Coin<SUI>,
/// MANAGED coins held in the reserve
managed: Coin<MANAGED>,
}

/// Needed to deposit a 1:1 ratio of SUI and MANAGED for minting, but deposited a different ratio
const EBAD_DEPOSIT_RATIO: u64 = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we need to figure out a canonical way of ordering module members.


fun init(ctx: &mut TxContext) {
// Get a treasury cap for the coin put it in the reserve
let treasury_cap = Coin::create_currency<BASKET>(BASKET{}, ctx);
Transfer::share_object(Reserve {
id: TxContext::new_id(ctx),
treasury_cap,
sui: Coin::zero<SUI>(ctx),
managed: Coin::zero<MANAGED>(ctx),
})
}

/// === Writes ===

/// Mint BASKET coins by accepting an equal number of SUI and MANAGED coins
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand how a Basket works. Why providing an equal number of SUI and MANAGED will generate an equal number of BASKET? Does the pricing of these coins not play a role?

Copy link
Contributor

@damirka damirka Apr 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this system is similar to Uniswap where you get a liquidity token for providing liquidity to one or both of the sides of the pool. It can be improved to support flexible rates for each of the currency. But that's my guess. 😆

Copy link
Collaborator Author

@sblackshear sblackshear Apr 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is that the value of BASKET fluctuates with the value of the assets in the reserve.

For a real-life example, SDR is a synthetic currency composed of a basket of USD, EUR, RMB, JPY, and GBP. I will add a comment about this + a link

Similarly, you might want a synthetic currency backed by a basket of NFT's that changes over time (e.g., some DAO's do this https://www.coindesk.com/markets/2022/02/10/flamingodaos-nft-portfolio-is-now-worth-1b/). Probably need a separate example to show how this would work, though.

public fun mint(
reserve: &mut Reserve, sui: Coin<SUI>, managed: Coin<MANAGED>, ctx: &mut TxContext
): Coin<BASKET> {
let num_sui = Coin::value(&sui);
assert!(num_sui == Coin::value(&managed), EBAD_DEPOSIT_RATIO);

Coin::join(&mut reserve.sui, sui);
Coin::join(&mut reserve.managed, managed);
Coin::mint(num_sui, &mut reserve.treasury_cap, ctx)
}

/// Burn BASKET coins and return the underlying reserve assets
public fun burn(
reserve: &mut Reserve, basket: Coin<BASKET>, ctx: &mut TxContext
): (Coin<SUI>, Coin<MANAGED>) {
let num_basket = Coin::value(&basket);
Coin::burn(basket, &mut reserve.treasury_cap);
let sui = Coin::withdraw(&mut reserve.sui, num_basket, ctx);
let managed = Coin::withdraw(&mut reserve.managed, num_basket, ctx);
(sui, managed)
}

// === Reads ===

/// Return the number of `MANAGED` coins in circulation
public fun total_supply(reserve: &Reserve): u64 {
Coin::total_supply(&reserve.treasury_cap)
}

/// Return the number of SUI in the reserve
public fun sui_supply(reserve: &Reserve): u64 {
Coin::value(&reserve.sui)
}

/// Return the number of MANAGED in the reserve
public fun managed_supply(reserve: &Reserve): u64 {
Coin::value(&reserve.managed)
}

#[test_only]
public fun init_for_testing(ctx: &mut TxContext) {
init(ctx)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@ module FungibleTokens::MANAGED {
/// and has no fields. The full type of the coin defined by this module will be `COIN<MANAGED>`.
struct MANAGED has drop {}

/// Register the trusted currency to acquire its `TreasuryCap`. Because
/// Register the managed currency to acquire its `TreasuryCap`. Because
/// this is a module initializer, it ensures the currency only gets
/// registered once.
fun init(ctx: &mut TxContext) {
// Get a treasury cap for the coin and give it to the transaction
// sender
// Get a treasury cap for the coin and give it to the transaction sender
let treasury_cap = Coin::create_currency<MANAGED>(MANAGED{}, ctx);
Transfer::transfer(treasury_cap, TxContext::sender(ctx))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

#[test_only]
module FungibleTokens::BASKETTests {
use FungibleTokens::BASKET::{Self, Reserve};
use FungibleTokens::MANAGED::MANAGED;
use Sui::Coin;
use Sui::SUI::SUI;
use Sui::TestScenario;

#[test]
public fun test_mint_burn() {
let user = @0xA;

let scenario = &mut TestScenario::begin(&user);
{
let ctx = TestScenario::ctx(scenario);
BASKET::init_for_testing(ctx);
};
TestScenario::next_tx(scenario, &user);
{
let reserve = TestScenario::remove_object<Reserve>(scenario);
let ctx = TestScenario::ctx(scenario);
assert!(BASKET::total_supply(&reserve) == 0, 0);

let num_coins = 10;
let sui = Coin::mint_for_testing<SUI>(num_coins, ctx);
let managed = Coin::mint_for_testing<MANAGED>(num_coins, ctx);
let basket = BASKET::mint(&mut reserve, sui, managed, ctx);
assert!(Coin::value(&basket) == num_coins, 1);
assert!(BASKET::total_supply(&reserve) == num_coins, 2);

let (sui, managed) = BASKET::burn(&mut reserve, basket, ctx);
assert!(Coin::value(&sui) == num_coins, 3);
assert!(Coin::value(&managed) == num_coins, 4);

Coin::keep(sui, ctx);
Coin::keep(managed, ctx);
TestScenario::return_object(scenario, reserve);
}
}

}