-
Notifications
You must be signed in to change notification settings - Fork 11.3k
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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). |
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; | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 😆 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
@@ -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); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
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.