From 7901f0a2b29f5e8f852fe8bdbbcaddc9c9d3e2f1 Mon Sep 17 00:00:00 2001 From: Maksim Kurnikov Date: Fri, 6 Nov 2020 11:58:22 +0300 Subject: [PATCH] set account balance via meta --- executor/src/execution.rs | 9 +- executor/src/meta.rs | 18 +++ executor/src/oracles.rs | 20 ++- executor/tests/test_executor.rs | 51 ++++++- resources/assets/stdlib/account.move | 203 ++++++++++++++++++++++++++ resources/assets/stdlib/dfinance.move | 185 +++++++++++++++++++++++ resources/assets/stdlib/event.move | 2 +- resources/assets/stdlib/signer.move | 4 + 8 files changed, 483 insertions(+), 9 deletions(-) create mode 100644 resources/assets/stdlib/account.move create mode 100644 resources/assets/stdlib/dfinance.move diff --git a/executor/src/execution.rs b/executor/src/execution.rs index 6db04509..19fd615f 100644 --- a/executor/src/execution.rs +++ b/executor/src/execution.rs @@ -16,7 +16,7 @@ use vm::file_format::{CompiledScript, FunctionDefinitionIndex}; use crate::explain::{explain_effects, explain_error, StepExecutionResult}; use crate::meta::ExecutionMeta; -use crate::oracles::{oracle_coins_module, time_metadata}; +use crate::oracles::{oracle_coins_module, time_metadata, coin_balance_metadata}; use move_vm_runtime::logging::NoContextLog; use crate::session::ConstsMap; @@ -168,6 +168,7 @@ pub fn execute_script( let mut ds = data_store.clone(); let ExecutionMeta { signers, + accounts_balance, oracle_prices, current_time, aborts_with, @@ -193,6 +194,12 @@ pub fn execute_script( ds.resources .insert((std_addr, price_tag), lcs::to_bytes(&val).unwrap()); } + for (account, coin, val) in accounts_balance { + ds.resources.insert( + (account, coin_balance_metadata(&coin)), + lcs::to_bytes(&val).unwrap(), + ); + } let res = execute_script_with_runtime_session( &ds, diff --git a/executor/src/meta.rs b/executor/src/meta.rs index 1ea3e046..f977c99d 100644 --- a/executor/src/meta.rs +++ b/executor/src/meta.rs @@ -20,6 +20,7 @@ fn split_signers(s: &str) -> Vec { #[derive(Debug, Default, Clone)] pub struct ExecutionMeta { pub signers: Vec, + pub accounts_balance: Vec<(AccountAddress, String, u128)>, pub oracle_prices: Vec<(StructTag, u128)>, pub current_time: Option, pub aborts_with: Option, @@ -34,6 +35,23 @@ impl ExecutionMeta { let (key, val) = split_around(&comment, ":"); match key { "signers" => self.signers = split_signers(val), + "balance" => { + if !val.contains(' ') { + eprintln!("Invalid balance doc comment: {}", comment); + return; + } + let (address, balance) = split_around(val, " "); + if !balance.contains(' ') { + eprintln!("Invalid balance doc comment: {}", comment); + return; + } + let (coin, num) = split_around(balance, " "); + self.accounts_balance.push(( + AccountAddress::from_hex_literal(address).unwrap(), + coin.to_string(), + num.parse().unwrap(), + )); + } "price" => { if !val.contains(' ') { eprintln!("Invalid ticker price doc comment: {}", comment); diff --git a/executor/src/oracles.rs b/executor/src/oracles.rs index 03e09ad1..a6feabf6 100644 --- a/executor/src/oracles.rs +++ b/executor/src/oracles.rs @@ -5,6 +5,9 @@ use move_core_types::account_address::AccountAddress; const COIN_MODULE: &str = "Coins"; const PRICE_STRUCT: &str = "Price"; +const ACCOUNT_MODULE: &str = "Account"; +const ACCOUNT_BALANCE_STRUCT: &str = "Balance"; + const XFI_MODULE: &str = "XFI"; const XFI_RESOURCE: &str = "T"; @@ -20,15 +23,15 @@ fn currency_type(curr: &str) -> TypeTag { if curr == XFI_MODULE { TypeTag::Struct(StructTag { address: CORE_CODE_ADDRESS, - name: Identifier::new(XFI_RESOURCE).expect("Valid currency name."), module: Identifier::new(XFI_MODULE).expect("Valid module name."), + name: Identifier::new(XFI_RESOURCE).expect("Valid currency name."), type_params: vec![], }) } else { TypeTag::Struct(StructTag { address: CORE_CODE_ADDRESS, - name: Identifier::new(curr).expect("Valid currency name."), module: Identifier::new(COIN_MODULE).expect("Valid module name."), + name: Identifier::new(curr).expect("Valid currency name."), type_params: vec![], }) } @@ -43,8 +46,8 @@ pub fn oracle_coins_module() -> ModuleId { pub fn oracle_metadata(first: &str, second: &str) -> StructTag { StructTag { address: CORE_CODE_ADDRESS, - name: Identifier::new(PRICE_STRUCT).expect("Valid struct name."), module: Identifier::new(COIN_MODULE).expect("Valid module name."), + name: Identifier::new(PRICE_STRUCT).expect("Valid struct name."), type_params: vec![currency_type(first), currency_type(second)], } } @@ -52,8 +55,17 @@ pub fn oracle_metadata(first: &str, second: &str) -> StructTag { pub fn time_metadata() -> StructTag { StructTag { address: CORE_CODE_ADDRESS, - name: Identifier::new("CurrentTimestamp").expect("Valid module name."), module: Identifier::new("Time").expect("Valid module name."), + name: Identifier::new("CurrentTimestamp").expect("Valid module name."), type_params: vec![], } } + +pub fn coin_balance_metadata(currency: &str) -> StructTag { + StructTag { + address: CORE_CODE_ADDRESS, + module: Identifier::new(ACCOUNT_MODULE).expect("Valid module name."), + name: Identifier::new(ACCOUNT_BALANCE_STRUCT).expect("Valid module name."), + type_params: vec![currency_type(currency)], + } +} diff --git a/executor/tests/test_executor.rs b/executor/tests/test_executor.rs index 63194ec3..e5325c5d 100644 --- a/executor/tests/test_executor.rs +++ b/executor/tests/test_executor.rs @@ -3,7 +3,7 @@ use move_executor::execute_script; use move_executor::explain::{AddressResourceChanges, ResourceChange}; use lang::compiler::ConstPool; use lang::compiler::file::MoveFile; -use resources::assets_dir; +use resources::{assets_dir, stdlib_path, modules_path}; use lang::compiler::error::CompilerError; fn script_path() -> String { @@ -19,11 +19,11 @@ fn module_path(name: &str) -> String { } pub fn stdlib_mod(name: &str) -> MoveFile { - MoveFile::load(assets_dir().join("stdlib").join(name)).unwrap() + MoveFile::load(stdlib_path().join(name)).unwrap() } pub fn modules_mod(name: &str) -> MoveFile { - MoveFile::load(assets_dir().join("modules").join(name)).unwrap() + MoveFile::load(modules_path().join(name)).unwrap() } #[test] @@ -962,3 +962,48 @@ script { ResourceChange("0x2::Record::T".to_string(), Some("[U8(11)]".to_string())) ); } + +#[test] +fn test_set_balance_via_meta() { + let _pool = ConstPool::new(); + let text = r#" +/// signers: 0x1 +script { + use 0x1::Dfinance; + use 0x1::Coins::ETH; + + fun register_coins(standard_account: &signer) { + Dfinance::register_coin(standard_account, b"eth", 18); + } +} + +/// signers: 0x2 +/// balance: 0x2 eth 100 +script { + use 0x1::Account; + use 0x1::Coins::ETH; + + fun main(s: &signer) { + assert(Account::balance(s) == 100, 101); + } +} + "#; + + execute_script( + MoveFile::with_content(script_path(), text), + vec![ + stdlib_mod("coins.move"), + stdlib_mod("event.move"), + stdlib_mod("dfinance.move"), + stdlib_mod("signer.move"), + stdlib_mod("account.move"), + ], + "libra", + "0x1", + vec![], + ) + .unwrap() + .last() + .unwrap() + .effects(); +} diff --git a/resources/assets/stdlib/account.move b/resources/assets/stdlib/account.move new file mode 100644 index 00000000..ad04434e --- /dev/null +++ b/resources/assets/stdlib/account.move @@ -0,0 +1,203 @@ +address 0x1 { + +/// Account is the access point for assets flow. It holds withdraw-deposit handlers +/// for generic currency . It also stores log of sent and received events +/// for every account. +module Account { + + use 0x1::Dfinance; + use 0x1::Signer; + use 0x1::Event; + + const ERR_ZERO_DEPOSIT: u64 = 7; + + /// holds account data, currently, only events + resource struct T {} + + resource struct Balance { + coin: Dfinance::T + } + + /// Message for sent events + struct SentPaymentEvent { + amount: u128, + denom: vector, + payee: address, + metadata: vector, + } + + /// Message for received events + struct ReceivedPaymentEvent { + amount: u128, + denom: vector, + payer: address, + metadata: vector, + } + + /// Init wallet for measurable currency, hence accept currency + public fun accept(account: &signer) { + move_to>(account, Balance { coin: Dfinance::zero() }) + } + + public fun has_balance(payee: address): bool { + exists>(payee) + } + + public fun has_account(payee: address): bool { + exists(payee) + } + + public fun balance(account: &signer): u128 acquires Balance { + balance_for(Signer::address_of(account)) + } + + public fun balance_for(addr: address): u128 acquires Balance { + Dfinance::value(&borrow_global>(addr).coin) + } + + public fun deposit_to_sender( + account: &signer, + to_deposit: Dfinance::T + ) acquires Balance { + deposit( + account, + Signer::address_of(account), + to_deposit + ) + } + + public fun deposit( + account: &signer, + payee: address, + to_deposit: Dfinance::T + ) acquires Balance { + deposit_with_metadata( + account, + payee, + to_deposit, + b"" + ) + } + + public fun deposit_with_metadata( + account: &signer, + payee: address, + to_deposit: Dfinance::T, + metadata: vector + ) acquires Balance { + deposit_with_sender_and_metadata( + account, + payee, + to_deposit, + metadata + ) + } + + public fun pay_from_sender( + account: &signer, + payee: address, + amount: u128 + ) acquires Balance { + pay_from_sender_with_metadata( + account, payee, amount, b"" + ) + } + + public fun pay_from_sender_with_metadata( + account: &signer, + payee: address, + amount: u128, + metadata: vector + ) + acquires Balance { + deposit_with_metadata( + account, + payee, + withdraw_from_sender(account, amount), + metadata + ) + } + + fun deposit_with_sender_and_metadata( + sender: &signer, + payee: address, + to_deposit: Dfinance::T, + metadata: vector + ) acquires Balance { + let amount = Dfinance::value(&to_deposit); + assert(amount > 0, ERR_ZERO_DEPOSIT); + + let denom = Dfinance::denom(); + + // add event as sent into account + Event::emit( + sender, + SentPaymentEvent { + amount, // u64 can be copied + payee, + denom: copy denom, + metadata: copy metadata + }, + ); + + // there's no way to improve this place as payee is not sender :( + if (!has_balance(payee)) { + create_balance(payee); + }; + + if (!has_account(payee)) { + create_account(payee); + }; + + let payee_balance = borrow_global_mut>(payee); + + // send money to payee + Dfinance::deposit(&mut payee_balance.coin, to_deposit); + // update payee's account with new event + Event::emit( + sender, + ReceivedPaymentEvent { + amount, + denom, + metadata, + payer: Signer::address_of(sender) + } + ) + } + + public fun withdraw_from_sender( + account: &signer, + amount: u128 + ): Dfinance::T acquires Balance { + let balance = borrow_global_mut>(Signer::address_of(account)); + + withdraw_from_balance(balance, amount) + } + + fun withdraw_from_balance(balance: &mut Balance, amount: u128): Dfinance::T { + Dfinance::withdraw(&mut balance.coin, amount) + } + + fun create_balance(addr: address) { + let sig = create_signer(addr); + + move_to>(&sig, Balance { + coin: Dfinance::zero() + }); + + destroy_signer(sig); + } + + /// keep this function, we may use T in the future + fun create_account(addr: address) { + let sig = create_signer(addr); + + move_to(&sig, T { }); + + destroy_signer(sig); + } + + native fun create_signer(addr: address): signer; + native fun destroy_signer(sig: signer); +} +} diff --git a/resources/assets/stdlib/dfinance.move b/resources/assets/stdlib/dfinance.move new file mode 100644 index 00000000..f762791a --- /dev/null +++ b/resources/assets/stdlib/dfinance.move @@ -0,0 +1,185 @@ +address 0x1 { + +/// Dfinance is a governance module which handles balances merging. It's basically +/// a mediator or wrapper around money-related operations. It holds knowledge about +/// registered coins and rules of their usage. Also it lessens load from 0x1::Account +module Dfinance { + + use 0x1::Signer; + use 0x1::Event; + + const ERR_NOT_CORE_ADDRESS: u64 = 101; + const ERR_NOT_REGISTERED: u64 = 102; + const ERR_INVALID_NUMBER_OF_DECIMALS: u64 = 103; + const ERR_AMOUNT_TOO_SMALL: u64 = 104; + const ERR_NON_ZERO_DEPOSIT: u64 = 105; + + resource struct T { + value: u128 + } + + resource struct Info { + denom: vector, + decimals: u8, + + // for tokens + is_token: bool, + owner: address, + total_supply: u128 + } + + public fun value(coin: &T): u128 { + coin.value + } + + public fun zero(): T { + T { value: 0 } + } + + public fun split(coin: T, amount: u128): (T, T) { + let other = withdraw(&mut coin, amount); + (coin, other) + } + + public fun join(coin1: T, coin2: T): T { + deposit(&mut coin1, coin2); + coin1 + } + + public fun deposit(coin: &mut T, check: T) { + let T { value } = check; // destroy check + coin.value = coin.value + value; + } + + public fun withdraw(coin: &mut T, amount: u128): T { + assert(coin.value >= amount, ERR_AMOUNT_TOO_SMALL); + coin.value = coin.value - amount; + T { value: amount } + } + + public fun destroy_zero(coin: T) { + let T { value } = coin; + assert(value == 0, ERR_NON_ZERO_DEPOSIT) + } + + /// Working with CoinInfo - coin registration procedure, 0x1 account used + + /// What can be done here: + /// - proposals API: user creates resource Info, pushes it into queue + /// 0x1 government reads and registers proposed resources by taking them + /// - try to find the way to share Info using custom module instead of + /// writing into main register (see above) + + /// getter for denom. reads denom information from 0x1 resource + public fun denom(): vector acquires Info { + *&borrow_global>(0x1).denom + } + + /// getter for currency decimals + public fun decimals(): u8 acquires Info { + borrow_global>(0x1).decimals + } + + /// getter for is_token property of Info + public fun is_token(): bool acquires Info { + borrow_global>(0x1).is_token + } + + /// getter for total_supply property of Info + public fun total_supply(): u128 acquires Info { + borrow_global>(0x1).total_supply + } + + /// getter for owner property of Info + public fun owner(): address acquires Info { + borrow_global>(0x1).owner + } + + /// only 0x1 address and add denom descriptions, 0x1 holds information resource + public fun register_coin(account: &signer, denom: vector, decimals: u8) { + assert_can_register_coin(account); + + move_to>(account, Info { + denom, + decimals, + + owner: 0x1, + total_supply: 0, + is_token: false + }); + } + + /// check whether sender is 0x1, helper method + fun assert_can_register_coin(account: &signer) { + assert(Signer::address_of(account) == 0x1, ERR_NOT_CORE_ADDRESS); + } + + const DECIMALS_MIN : u8 = 0; + const DECIMALS_MAX : u8 = 18; + + /// Token resource. Must be used with custom token type. Which means + /// that first token creator must deploy a token module which will have + /// empty type in it which should be then passed as type argument + /// into Token::initialize() method. + resource struct Token {} + + /// This is the event data for TokenCreated event which can only be fired + /// from this module, from Token::initialize() method. + struct TokenCreatedEvent { + creator: address, + total_supply: u128, + denom: vector, + decimals: u8 + } + + /// Initialize token. For this method to work user must provide custom + /// resource type which he had previously created within his own module. + public fun create_token( + account: &signer, + total_supply: u128, + decimals: u8, + denom: vector + ): T> { + + // check if this token has never been registered + assert(!exists>(0x1), ERR_NOT_REGISTERED); + + // no more than DECIMALS MAX is allowed + assert(decimals >= DECIMALS_MIN && decimals <= DECIMALS_MAX, ERR_INVALID_NUMBER_OF_DECIMALS); + + let owner = Signer::address_of(account); + + register_token_info(Info { + denom: copy denom, + decimals, + owner, + total_supply, + is_token: true + }); + + // finally fire the TokenEmitted event + Event::emit>( + account, + TokenCreatedEvent { + creator: owner, + total_supply, + decimals, + denom + } + ); + + T> { value: total_supply } + } + + /// Created Info resource must be attached to 0x1 address. + /// Keeping this public until native function is ready. + fun register_token_info(info: Info) { + let sig = create_signer(0x1); + move_to>(&sig, info); + destroy_signer(sig); + } + + native fun create_signer(addr: address): signer; + native fun destroy_signer(sig: signer); +} +} diff --git a/resources/assets/stdlib/event.move b/resources/assets/stdlib/event.move index 5e97d75b..36bb295d 100644 --- a/resources/assets/stdlib/event.move +++ b/resources/assets/stdlib/event.move @@ -4,6 +4,6 @@ module Event { // Native procedure that writes to the actual event stream in Event store // This will replace the "native" portion of EmitEvent bytecode - native public fun emit(msg: T); + native public fun emit(account: &signer, msg: T); } } diff --git a/resources/assets/stdlib/signer.move b/resources/assets/stdlib/signer.move index f690f2b3..e85885b5 100644 --- a/resources/assets/stdlib/signer.move +++ b/resources/assets/stdlib/signer.move @@ -13,5 +13,9 @@ module Signer { public fun address_of(s: &signer): address { *borrow_address(s) } + + spec module { + native define get_address(account: signer): address; + } } }