Skip to content

Commit

Permalink
V5 Decimal numeric type in Move Vm (#654)
Browse files Browse the repository at this point in the history
* add decimal native

* ol_decimal.rs tests

* decimal irrational number test

* change move decimal type to u128 to use throughout.

* patch negative number casting
  • Loading branch information
0o-de-lally authored Sep 21, 2021
1 parent 8fe8ca4 commit e042010
Show file tree
Hide file tree
Showing 107 changed files with 3,569 additions and 1,365 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

169 changes: 169 additions & 0 deletions language/diem-framework/modules/0L/Decimal.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
address 0x1 {
module Decimal {
// The Move Decimal data structure is optimized for readability and compatibility.
// In particular it is indended for compatibility with the underlying rust_decimal crate https://github.com/paupino/rust-decimal. In that library a new decimal type is initialized with Decimal::from_i128_with_scale(mantissa: i128, scale: u32)
// Note: While the underlying Rust crate type has optimal storage characteristics, this Move decimal representation is NOT optimized for storage.

struct Decimal has key, store, drop {
sign: bool,
int: u128,
scale: u8, // max intger is number 28
}

// while stored in u128, the largest integer possible in the rust_decimal vm dependency is 2^96
const MAX_RUST_DECIMAL_U128: u128 = 79228162514264337593543950335;

// pair decimal ops
const ADD: u8 = 1;
const SUB: u8 = 2;
const MUL: u8 = 3;
const DIV: u8 = 4;
const POW: u8 = 5;
const ROUND: u8 = 6;

// single ops
const SQRT: u8 = 100;
const TRUNC: u8 = 101;

const ROUND_MID_TO_EVEN: u8 = 0; // This is the default in the rust_decimal lib.
const ROUND_MID_FROM_ZERO: u8 = 1;



native public fun decimal_demo(sign: bool, int: u128, scale: u8): (bool, u128, u8);

native public fun single_op(op_id: u8, sign: bool, int: u128, scale: u8): (bool, u128, u8);

native public fun pair_op(
op_id: u8,
rounding_strategy_id: u8,
// left number
sign_1: bool,
int_1: u128,
scale_1: u8,
// right number
sign_2: bool,
int_2: u128,
scale_3: u8
): (bool, u128, u8);

public fun new(sign: bool, int: u128, scale: u8): Decimal {

assert(int < MAX_RUST_DECIMAL_U128, 01);

// check scale < 28
assert(scale < 28, 02);

return Decimal {
sign: sign,
int: int,
scale: scale
}
}

/////// SUGAR /////////
public fun trunc(d: &Decimal): Decimal {
let (sign, int, scale) = single_op(TRUNC, *&d.sign, *&d.int, *&d.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

public fun sqrt(d: &Decimal): Decimal {
let (sign, int, scale) = single_op(SQRT, *&d.sign, *&d.int, *&d.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

public fun add(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(ADD, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

public fun sub(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(SUB, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}
public fun mul(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(MUL, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

public fun div(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(DIV, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}


public fun rescale(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(0, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

public fun round(l: &Decimal, r: &Decimal, strategy: u8): Decimal {
let (sign, int, scale) = pair_op(ROUND, strategy, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}


public fun power(l: &Decimal, r: &Decimal): Decimal {
let (sign, int, scale) = pair_op(POW, ROUND_MID_TO_EVEN, *&l.sign, *&l.int, *&l.scale, *&r.sign, *&r.int, *&r.scale);
return Decimal {
sign: sign,
int: int,
scale: scale,
}
}

///// GETTERS /////

// unwrap creates a new decimal instance
public fun unwrap(d: &Decimal): (bool, u128, u8) {
return (*&d.sign, *&d.int, *&d.scale)
}

// borrow sign
public fun borrow_sign(d: &Decimal): &bool {
return &d.sign
}

// borrows the value of the integer
public fun borrow_int(d: &Decimal): &u128 {
return &d.int
}

// borrow sign
public fun borrow_scale(d: &Decimal): &u8 {
return &d.scale
}
}
}
179 changes: 179 additions & 0 deletions language/diem-framework/modules/0L/Demo_Bonding.move
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
address 0x1 {
module Bonding {
// use 0x1::Signer;
use 0x1::Decimal;
use 0x1::Debug::print;

struct CurveState has key {
is_deprecated: bool,
reserve: u128, //todo: change to Diem<XUS>,
supply_issued: u128,
}

struct Token has key, store {
value: u128
}

// fun sunset() {
// // if true state.is_deprecated == true
// // allow holders to redeem at the spot price at sunset.
// // cannot receive new deposits
// //TBD
// }

///////// Initialization /////////
public fun initialize_curve(
service: &signer,
deposit: u128, // Diem<XUS>,
supply_init: u128,
) {
// let deposit_value = Diem::value<XUS>(&deposit);
assert(deposit > 0, 7357001);

let init_state = CurveState {
is_deprecated: false, // deprecate mode
reserve: deposit,
supply_issued: supply_init,
};

// This initializes the contract, and stores the contract state at the address of sender. TDB where the state gets stored.
move_to<CurveState>(service, init_state);

let first_token = Token {
value: supply_init
};

// minting the first coin, sponsor is recipent of initial coin.
move_to<Token>(service, first_token);
}

/////////// Calculations /////////
public fun deposit_calc(add_to_reserve: u128, reserve: u128, supply: u128): u128 {

let one = Decimal::new(true, 1, 0);
print(&one);

let add_dec = Decimal::new(true, add_to_reserve, 0);
print(&add_dec);

let reserve_dec = Decimal::new(true, reserve, 0);
print(&reserve_dec);

let supply_dec = Decimal::new(true, supply, 0);
print(&supply_dec);

// formula:
// supply * sqrt(one+(add_to_reserve/reserve))

let a = Decimal::div(&add_dec, &reserve_dec);
print(&a);
let b = Decimal::add(&one, &a);
print(&b);
let c = Decimal::sqrt(&b);
print(&c);
let d = Decimal::mul(&supply_dec, &c);
print(&d);
let int = Decimal::borrow_int(&Decimal::trunc(&d));
print(int);

return *int
}

// fun withdraw_curve(remove_from_supply: u128, supply: u128, reserve: u128):u128 {
// // TODO:
// // formula: reserve * (one - remove_from_supply/supply )^2
// // let one = Decimal::new(true, 1, 0);
//
//
// }


///////// API /////////
// this simulates the depositing and getting a minted token out, but just using integers, not coin types for now.
public fun test_bond_to_mint(_sender: &signer, service_addr: address, deposit: u128): u128 acquires CurveState {
assert(exists<CurveState>(service_addr), 73570002);
let state = borrow_global_mut<CurveState>(service_addr);

let post_supply = deposit_calc(deposit, state.reserve, state.supply_issued);
print(&post_supply);
assert(post_supply > state.supply_issued, 73570003);
let mint = post_supply - state.supply_issued;
print(&mint);
// update the new curve state
state.reserve = state.reserve + deposit;
state.supply_issued = state.supply_issued + mint;
// print(&state);
mint
}

// public fun burn_to_withdraw(sender: &signer, service_addr: address, burn_value: u128):Decimal acquires CurveState, Token {

// assert(exists<CurveState>(service_addr), 73570002);
// let sender_addr = Signer::address_of(sender);
// assert(exists<Token>(sender_addr), 73570003);
// assert(Coin::balance(sender_addr) >= burn_value, 73570004);

// let state = borrow_global_mut<CurveState>(service_addr);

// // Calculate the reserve change.
// let remove_from_supply = Decimal::new(burn_value);
// let withdraw_value = withdraw_curve(remove_from_supply, service_addr, state.reserve);

// withdraw_token_from(sender, burn_value);
// // new curve state
// state.reserve = state.reserve - withdraw_value;
// state.supply = state.supply - burn_value;
// withdraw_value
// }


// // Merges a GAS coin.
// fun deposit_gas_and_merge(sender: &signer, coin: GAS) acquires Token {
// //TODO: merges gas coin to bonding curve reserve
// }

// // Splits a coin to be used.
// fun withdraw_token_and_split_gas(sender: &signer, sub_value: Decimal) acquires Token {
// //TODO:
// }


// ///////// GETTERS /////////

public fun get_curve_state(sponsor_address: address): (u128, u128) acquires CurveState {
let state = borrow_global<CurveState>(sponsor_address);
(state.reserve, state.supply_issued)
}

// public fun get_user_balance(addr: address): Decimal acquires Token {
// let state = borrow_global<Token>(addr);
// state.value
// }

// // This is a steady state getter
// public fun calc_spot_price_from_state(sponsor_addr: address): Decimal acquires CurveState {
// let state = borrow_global_mut<CurveState>(sponsor_addr);
// state.kappa * (state.reserve/state.supply)
// }


// ///////// TEST /////////

// // // NOTE: This "invariant" may not be invariant with rounding issues.
// // public fun test_get_curve_invariant(sponsor_addr: address):Decimal acquires CurveState {
// // let state = borrow_global_mut<CurveState>(sponsor_addr);
// // let two = FixedPoint32::create_from_raw_value(2);
// // let zero = FixedPoint32::create_from_raw_value(0);

// // // TOOD: when we have native math lib the formula will be:
// // // (state.supply, to power of state.kappa) / state.reserve
// // if (state.kappa == two ) {
// // return (state.supply * state.supply) / state.reserve
// // };
// // zero
// // }



}
}
2 changes: 1 addition & 1 deletion language/diem-framework/modules/doc/Cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ set and/or jailed. To be compliant, validators must be BOTH validating and minin
<a href="Roles.md#0x1_Roles_assert_diem_root">Roles::assert_diem_root</a>(vm);
// did the validator sign blocks above threshold?
<b>let</b> signs = <a href="Stats.md#0x1_Stats_node_above_thresh">Stats::node_above_thresh</a>(vm, node_addr, height_start, height_end);
<b>let</b> mines = <a href="MinerState.md#0x1_MinerState_node_above_thresh">MinerState::node_above_thresh</a>(vm, node_addr);
<b>let</b> mines = <a href="MinerState.md#0x1_MinerState_node_above_thresh">MinerState::node_above_thresh</a>(node_addr);

<b>if</b> (signs && mines) {
<a href="Cases.md#0x1_Cases_VALIDATOR_COMPLIANT">VALIDATOR_COMPLIANT</a> // compliant: in next set, gets paid, weight increments
Expand Down
Loading

0 comments on commit e042010

Please sign in to comment.