Skip to content

Commit

Permalink
feat: its scale coins and flow limits (#64)
Browse files Browse the repository at this point in the history
Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
Foivos and milapsheth authored Jul 31, 2024
1 parent 72579e5 commit 69a4773
Show file tree
Hide file tree
Showing 14 changed files with 717 additions and 642 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,7 @@ target/
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

# dist
dist
# move_compile
move_compile/
31 changes: 22 additions & 9 deletions move/its/sources/coin_info.move
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,38 @@ module its::coin_info {
use std::ascii;
use std::string::String;

use sui::coin::{Self, CoinMetadata};
use sui::coin::CoinMetadata;

use its::utils;

public struct CoinInfo<phantom T> has store {
name: String,
symbol: ascii::String,
decimals: u8,
remote_decimals: u8,
metadata: Option<CoinMetadata<T>>,
}

/// Create a new coin info from the given name, symbol and decimals.
public fun from_info<T>(
name: String, symbol: ascii::String, decimals: u8
name: String, symbol: ascii::String, decimals: u8, remote_decimals: u8,
): CoinInfo<T> {
CoinInfo {
name,
symbol,
decimals,
remote_decimals,
metadata: option::none(),
}
}

/// Create a new coin info from the given `CoinMetadata` object.
public fun from_metadata<T>(metadata: CoinMetadata<T>): CoinInfo<T> {
public fun from_metadata<T>(metadata: CoinMetadata<T>, remote_decimals: u8): CoinInfo<T> {
CoinInfo {
name: coin::get_name(&metadata),
symbol: coin::get_symbol(&metadata),
decimals: coin::get_decimals(&metadata),
name: metadata.get_name(),
symbol: metadata.get_symbol(),
decimals: metadata.get_decimals(),
remote_decimals,
metadata: option::some(metadata),
}
}
Expand All @@ -51,17 +56,25 @@ module its::coin_info {
self.decimals
}

public fun remote_decimals<T>(self: &CoinInfo<T>): u8 {
self.remote_decimals
}

public fun scaling<T>(self: &CoinInfo<T>): u256 {
utils::pow(10, self.remote_decimals - self.decimals)
}

public fun metadata<T>(self: &CoinInfo<T>): &Option<CoinMetadata<T>> {
&self.metadata
}

#[test_only]
public fun drop<T>(coin_info: CoinInfo<T>) {
let CoinInfo {name: _, symbol: _, decimals: _, metadata } = coin_info;
if (option::is_some(&metadata)) {
let CoinInfo {name: _, symbol: _, decimals: _, remote_decimals: _, metadata } = coin_info;
if (metadata.is_some()) {
abort 0
} else {
option::destroy_none(metadata)
metadata.destroy_none()
}
}
}
82 changes: 65 additions & 17 deletions move/its/sources/coin_management.move
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ module its::coin_management {

use sui::coin::{Self, TreasuryCap, Coin};
use sui::balance::{Self, Balance};
use sui::clock::Clock;

use its::flow_limit::{Self, FlowLimit};

/// Trying to add a distributor to a `CoinManagement` that does not
/// have a `TreasuryCap`.
Expand All @@ -14,6 +17,10 @@ module its::coin_management {
treasury_cap: Option<TreasuryCap<T>>,
balance: Option<Balance<T>>,
distributor: Option<address>,
operator: Option<address>,
flow_limit: FlowLimit,
scaling: u256,
dust: u256,
}

/// Create a new `CoinManagement` with a `TreasuryCap`.
Expand All @@ -23,6 +30,10 @@ module its::coin_management {
treasury_cap: option::some(treasury_cap),
balance: option::none(),
distributor: option::none(),
operator: option::none(),
flow_limit: flow_limit::new(),
scaling: 0, // placeholder, this gets edited when a coin is registered.
dust: 0,
}
}

Expand All @@ -33,6 +44,10 @@ module its::coin_management {
treasury_cap: option::none(),
balance: option::some(balance::zero()),
distributor: option::none(),
operator: option::none(),
flow_limit: flow_limit::new(),
scaling: 0, // placeholder, this gets edited when a coin is registered.
dust: 0,
}
}

Expand All @@ -43,34 +58,61 @@ module its::coin_management {
self.distributor.fill(distributor);
}

/// Adds the distributor address to the `CoinManagement`.
/// Only works for a `CoinManagement` with a `TreasuryCap`.
public fun add_operator<T>(self: &mut CoinManagement<T>, operator: address) {
self.operator.fill(operator);
}

// === Protected Methods ===

/// Takes the given amount of Coins from user.
public(package) fun take_coin<T>(self: &mut CoinManagement<T>, to_take: Coin<T>) {
/// Takes the given amount of Coins from user. Returns the amount that the ITS is supposed to give on other chains.
public(package) fun take_coin<T>(self: &mut CoinManagement<T>, to_take: Coin<T>, clock: &Clock): u256 {
self.flow_limit.add_flow_out(to_take.value(), clock);
let amount = (to_take.value() as u256) * self.scaling;
if (has_capability(self)) {
self.treasury_cap
.borrow_mut()
.burn(to_take);
self.burn(to_take);
} else {
self.balance
.borrow_mut()
.join(to_take.into_balance());
}
};
amount
}

/// Withdraws or mints the given amount of coins.
/// Withdraws or mints the given amount of coins. Any leftover amount from previous transfers is added to the coin here.
public(package) fun give_coin<T>(
self: &mut CoinManagement<T>, amount: u64, ctx: &mut TxContext
self: &mut CoinManagement<T>, mut amount: u256, clock: &Clock, ctx: &mut TxContext
): Coin<T> {
amount = amount + self.dust;
self.dust = amount % self.scaling;
let sui_amount = ( amount / self.scaling as u64);
self.flow_limit.add_flow_out(sui_amount, clock);
if (has_capability(self)) {
self.treasury_cap
.borrow_mut()
.mint(amount, ctx)
self.mint(sui_amount, ctx)
} else {
coin::take(self.balance.borrow_mut(), amount, ctx)
coin::take(self.balance.borrow_mut(), sui_amount, ctx)
}
}

// helper function to mint as a distributor.
public(package) fun mint<T>(self: &mut CoinManagement<T>, amount: u64, ctx: &mut TxContext): Coin<T> {
self.treasury_cap
.borrow_mut()
.mint(amount, ctx)
}

// helper function to burn as a distributor.
public(package) fun burn<T>(self: &mut CoinManagement<T>, coin: Coin<T>) {
self.treasury_cap
.borrow_mut()
.burn(coin);
}

public(package) fun set_scaling<T>(self: &mut CoinManagement<T>, scaling: u256) {
self.scaling = scaling;
}

// === Views ===

/// Checks if the given address is a `distributor`.
Expand Down Expand Up @@ -108,17 +150,19 @@ module its::coin_management {

let mut coin = cap.mint(amount1, ctx);
let mut management1 = new_locked<COIN_MANAGEMENT>();
management1.take_coin(coin);
let clock = sui::clock::create_for_testing(ctx);
management1.take_coin(coin, &clock);

assert!(management1.balance.borrow().value() == amount1, 0);

coin = cap.mint(amount2, ctx);
let mut management2 = new_with_cap<COIN_MANAGEMENT>(cap);
management2.take_coin(coin);
management2.take_coin(coin, &clock);

sui::test_utils::destroy(metadata);
sui::test_utils::destroy(management1);
sui::test_utils::destroy(management2);
sui::test_utils::destroy(clock);
}

#[test]
Expand All @@ -130,22 +174,26 @@ module its::coin_management {

let mut coin = cap.mint(amount1, ctx);
let mut management1 = new_locked<COIN_MANAGEMENT>();
management1.take_coin(coin);
coin = management1.give_coin(amount1, ctx);
management1.scaling = 1;
let clock = sui::clock::create_for_testing(ctx);
management1.take_coin(coin, &clock);
coin = management1.give_coin((amount1 as u256), &clock, ctx);

assert!(management1.balance.borrow().value() == 0, 0);
assert!(coin.value() == amount1, 0);

sui::test_utils::destroy(coin);

let mut management2 = new_with_cap<COIN_MANAGEMENT>(cap);
coin = management2.give_coin(amount2, ctx);
management2.scaling = 1;
coin = management2.give_coin((amount2 as u256), &clock, ctx);

assert!(coin.value() == amount2, 1);

sui::test_utils::destroy(coin);
sui::test_utils::destroy(metadata);
sui::test_utils::destroy(management1);
sui::test_utils::destroy(management2);
sui::test_utils::destroy(clock);
}
}
7 changes: 5 additions & 2 deletions move/its/sources/discovery.move
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ module its::discovery {

let arguments = vector[
arg,
vector[2]
vector[2],
vector[0, 6],
];


Expand Down Expand Up @@ -127,6 +128,7 @@ module its::discovery {
vector[
discovery_arg,
channel_id_arg,
vector[0, 6],
],
vector[],
)],
Expand Down Expand Up @@ -253,7 +255,8 @@ module its::discovery {

let arguments = vector[
arg,
vector[2]
vector[2],
vector[0, 6],
];
assert!(call_info.arguments() == arguments, 6);
assert!(call_info.type_arguments() == vector[type_arg.into_string()], 7);
Expand Down
52 changes: 52 additions & 0 deletions move/its/sources/flow_limit.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module its::flow_limit {
use sui::clock::Clock;

const EPOCH_TIME: u64 = 6 * 60 * 60 * 1000;

const EFlowLimitExceeded: u64 = 0;

public struct FlowLimit has store, copy, drop {
flow_limit: u64,
flow_in: u64,
flow_out: u64,
current_epoch: u64,
}

public(package) fun new(): FlowLimit {
FlowLimit {
flow_limit: 0,
flow_in: 0,
flow_out: 0,
current_epoch: 0,
}
}

fun update_epoch(self: &mut FlowLimit, clock: &Clock) {
let epoch = clock.timestamp_ms() / EPOCH_TIME;
if(epoch > self.current_epoch) {
self.current_epoch = epoch;
self.flow_in = 0;
self.flow_out = 0;
}
}

public(package) fun add_flow_in(self: &mut FlowLimit, amount: u64, clock: &Clock) {
if (self.flow_limit == 0) return;

update_epoch(self, clock);
assert!(self.flow_in + amount < self.flow_limit + self.flow_out, EFlowLimitExceeded);
self.flow_in = self.flow_in + amount;
}

public(package) fun add_flow_out(self: &mut FlowLimit, amount: u64, clock: &Clock) {
if (self.flow_limit == 0) return;

update_epoch(self, clock);
assert!(self.flow_out + amount < self.flow_limit + self.flow_in, EFlowLimitExceeded);
self.flow_out = self.flow_out + amount;
}

public(package) fun set_flow_limit(self: &mut FlowLimit, flow_limit: u64) {
self.flow_limit = flow_limit;
}
}
16 changes: 15 additions & 1 deletion move/its/sources/its.move
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ module its::its {
&self.registered_coins[token_id]
}

public(package) fun get_coin_data_mut<T>(self: &mut ITS, token_id: TokenId): &mut CoinData<T> {
assert!(self.registered_coins.contains(token_id), EUnregisteredCoin);
&mut self.registered_coins[token_id]
}

public(package) fun get_coin_scaling<T>(self: &CoinData<T>): u256 {
self.coin_info.scaling()
}

public fun get_coin_info<T>(self: &ITS, token_id: TokenId): &CoinInfo<T> {
&get_coin_data<T>(self, token_id).coin_info
}
Expand All @@ -112,6 +121,10 @@ module its::its {
get_coin_info<T>(self, token_id).decimals()
}

public fun token_remote_decimals<T>(self: &ITS, token_id: TokenId): u8 {
get_coin_info<T>(self, token_id).remote_decimals()
}

public fun get_trusted_address(self: &ITS, chain_name: String): String {
*self.address_tracker.get_trusted_address(chain_name)
}
Expand Down Expand Up @@ -169,9 +182,10 @@ module its::its {
public(package) fun add_registered_coin<T>(
self: &mut ITS,
token_id: TokenId,
coin_management: CoinManagement<T>,
mut coin_management: CoinManagement<T>,
coin_info: CoinInfo<T>,
) {
coin_management.set_scaling(coin_info.scaling());
self.registered_coins.add(token_id, CoinData<T> {
coin_management,
coin_info,
Expand Down
Loading

0 comments on commit 69a4773

Please sign in to comment.