diff --git a/Cargo.toml b/Cargo.toml index 3c90f80c2..8cd40b01c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,6 @@ members = [ "common/pubsub", "core/api", - "core/bank", "core/consensus", "core/executor", "core/mempool", diff --git a/core/bank/Cargo.toml b/core/bank/Cargo.toml deleted file mode 100644 index 15c288a06..000000000 --- a/core/bank/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "core-bank" -version = "0.1.0" -authors = ["Cryptape Technologies "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/core/bank/src/lib.rs b/core/bank/src/lib.rs deleted file mode 100644 index 31e1bb209..000000000 --- a/core/bank/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} diff --git a/core/consensus/src/fixed_types.rs b/core/consensus/src/fixed_types.rs index b6f85226c..1163187ee 100644 --- a/core/consensus/src/fixed_types.rs +++ b/core/consensus/src/fixed_types.rs @@ -155,7 +155,7 @@ mod test { use protocol::codec::ProtocolCodec; use protocol::types::{ - Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, UserAddress, + CarryingAsset, Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, UserAddress, }; use super::FixedSignedTxs; @@ -177,9 +177,11 @@ mod test { cycle: random::(), }; let action = TransactionAction::Transfer { - receiver: address.clone(), - asset_id: nonce.clone(), - amount: FromPrimitive::from_i32(42).unwrap(), + receiver: address.clone(), + carrying_asset: CarryingAsset { + asset_id: nonce.clone(), + amount: FromPrimitive::from_i32(42).unwrap(), + }, }; let mut raw = RawTransaction { chain_id: nonce.clone(), diff --git a/core/executor/src/adapter/contract.rs b/core/executor/src/adapter/contract.rs index bedd5aa64..acbb708a0 100644 --- a/core/executor/src/adapter/contract.rs +++ b/core/executor/src/adapter/contract.rs @@ -1,5 +1,7 @@ +use std::cell::RefCell; use std::collections::HashMap; use std::error::Error; +use std::rc::Rc; use bytes::Bytes; use derive_more::{Display, From}; @@ -9,19 +11,21 @@ use protocol::traits::executor::{ContractSchema, ContractSer}; use protocol::types::MerkleRoot; use protocol::{ProtocolError, ProtocolErrorKind, ProtocolResult}; -use crate::trie::MPTTrie; +use crate::trie::{MPTTrie, TrieDB}; -pub struct GeneralContractStateAdapter { - trie: MPTTrie, +pub type RcGeneralContractStateAdapter = Rc>>; - // TODO(@yejiayu): The value of "map" should be changed to Box to avoid multiple +pub struct GeneralContractStateAdapter { + trie: MPTTrie, + + // TODO(@yejiayu): The value of HashMap should be changed to Box to avoid multiple // serializations. cache_map: HashMap, stash_map: HashMap, } -impl GeneralContractStateAdapter { - pub fn new(trie: MPTTrie) -> Self { +impl GeneralContractStateAdapter { + pub fn new(trie: MPTTrie) -> Self { Self { trie, @@ -31,22 +35,24 @@ impl GeneralContractStateAdapter { } } -impl ContractStateAdapter for GeneralContractStateAdapter { +impl ContractStateAdapter for GeneralContractStateAdapter { fn get( &self, key: &::Key, ) -> ProtocolResult::Value>> { - if let Some(value_bytes) = self.cache_map.get(&key.encode()?) { + let encoded_key = key.encode()?; + + if let Some(value_bytes) = self.cache_map.get(&encoded_key) { let inst = <_>::decode(value_bytes.clone())?; return Ok(Some(inst)); } - if let Some(value_bytes) = self.stash_map.get(&key.encode()?) { + if let Some(value_bytes) = self.stash_map.get(&encoded_key) { let inst = <_>::decode(value_bytes.clone())?; return Ok(Some(inst)); } - if let Some(value_bytes) = self.trie.get(key.encode()?)? { + if let Some(value_bytes) = self.trie.get(&encoded_key)? { return Ok(Some(Schema::Value::decode(value_bytes)?)); } @@ -57,7 +63,17 @@ impl ContractStateAdapter for GeneralContractStateAdapter { &self, key: &::Key, ) -> ProtocolResult { - Ok(self.get::(key)?.is_some()) + let encoded_key = key.encode()?; + + if self.cache_map.contains_key(&encoded_key) { + return Ok(true); + }; + + if self.stash_map.contains_key(&encoded_key) { + return Ok(true); + }; + + self.trie.contains(&encoded_key) } fn insert_cache( diff --git a/core/executor/src/adapter/mod.rs b/core/executor/src/adapter/mod.rs index 83619856a..173ef481e 100644 --- a/core/executor/src/adapter/mod.rs +++ b/core/executor/src/adapter/mod.rs @@ -1,3 +1,5 @@ mod contract; -pub use contract::{GeneralContractStateAdapter, GeneralContractStateAdapterError}; +pub use contract::{ + GeneralContractStateAdapter, GeneralContractStateAdapterError, RcGeneralContractStateAdapter, +}; diff --git a/core/executor/src/cycles.rs b/core/executor/src/cycles.rs new file mode 100644 index 000000000..0bc665a7b --- /dev/null +++ b/core/executor/src/cycles.rs @@ -0,0 +1,59 @@ +use std::collections::HashMap; +use std::error::Error; + +use derive_more::{Display, From}; +use lazy_static::lazy_static; + +use protocol::types::Fee; +use protocol::{ProtocolError, ProtocolErrorKind, ProtocolResult}; + +const NATIVE_BASE_CYCLES: u64 = 10; + +lazy_static! { + static ref CYCLES_TABLE: HashMap = { + let mut table = HashMap::new(); + table.insert(CyclesAction::AccountTransfer, NATIVE_BASE_CYCLES * 21); + table.insert(CyclesAction::BankRegister, NATIVE_BASE_CYCLES * 210); + table + }; +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum CyclesAction { + AccountTransfer, + BankRegister, +} + +pub fn consume_cycles( + action: CyclesAction, + cycles_price: u64, + fee: &mut Fee, + limit: &Fee, +) -> ProtocolResult<()> { + let cycles_used = fee.cycle + + CYCLES_TABLE + .get(&action) + .unwrap_or_else(|| panic!("cycles action {:?} uninitialized", action)); + let cycles_used = cycles_used * cycles_price; + + if cycles_used > limit.cycle { + return Err(CyclesError::OutOfCycles.into()); + } + + fee.cycle = cycles_used; + Ok(()) +} + +#[derive(Debug, Display, From)] +pub enum CyclesError { + #[display(fmt = "out of cycles")] + OutOfCycles, +} + +impl Error for CyclesError {} + +impl From for ProtocolError { + fn from(err: CyclesError) -> ProtocolError { + ProtocolError::new(ProtocolErrorKind::Executor, Box::new(err)) + } +} diff --git a/core/executor/src/fixed_types/mod.rs b/core/executor/src/fixed_types/mod.rs index d3ff1582c..a0502d68e 100644 --- a/core/executor/src/fixed_types/mod.rs +++ b/core/executor/src/fixed_types/mod.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; use std::error::Error; -use std::mem; use bytes::Bytes; use derive_more::{Display, From}; @@ -186,7 +185,7 @@ impl rlp::Encodable for FixedAccount { Account::User(user) => { s.begin_list(3); s.append(&USER_ACCOUNT_FLAG); - s.append(&u64_to_vec(user.nonce)); + s.append(&user.nonce.to_be_bytes().to_vec()); let mut asset_list = Vec::with_capacity(user.assets.len()); @@ -205,7 +204,7 @@ impl rlp::Encodable for FixedAccount { Account::Contract(contract) => { s.begin_list(4); s.append(&CONTRACT_ACCOUNT_FLAG); - s.append(&u64_to_vec(contract.nonce)); + s.append(&contract.nonce.to_be_bytes().to_vec()); s.append(&contract.storage_root.as_bytes().to_vec()); let mut asset_list = Vec::with_capacity(contract.assets.len()); @@ -233,7 +232,7 @@ impl rlp::Decodable for FixedAccount { match flag { USER_ACCOUNT_FLAG => { - let nonce: u64 = r.at(1)?.as_val()?; + let nonce = bytes_to_u64(r.at(1)?.data()?); let asset_list: Vec = rlp::decode_list(r.at(2)?.as_raw()); let mut assets = BTreeMap::new(); @@ -250,7 +249,7 @@ impl rlp::Decodable for FixedAccount { }) } CONTRACT_ACCOUNT_FLAG => { - let nonce: u64 = r.at(1)?.as_val()?; + let nonce = bytes_to_u64(r.at(1)?.data()?); let storage_root_bytes = r.at(2)?.data()?; let asset_list: Vec = rlp::decode_list(r.at(3)?.as_raw()); @@ -403,11 +402,6 @@ impl rlp::Decodable for FixedContractAsset { } } -fn u64_to_vec(n: u64) -> Vec { - let nonce_bytes: [u8; 8] = unsafe { mem::transmute(n.to_be()) }; - nonce_bytes.to_vec() -} - #[derive(Debug, Display, From)] pub enum FixedTypesError { Decoder(rlp::DecoderError), @@ -420,3 +414,9 @@ impl From for ProtocolError { ProtocolError::new(ProtocolErrorKind::Executor, Box::new(err)) } } + +fn bytes_to_u64(bytes: &[u8]) -> u64 { + let mut nonce_bytes = [0u8; 8]; + nonce_bytes.copy_from_slice(bytes); + u64::from_be_bytes(nonce_bytes) +} diff --git a/core/executor/src/lib.rs b/core/executor/src/lib.rs index ade3f4fe6..c7a59d5de 100644 --- a/core/executor/src/lib.rs +++ b/core/executor/src/lib.rs @@ -1,5 +1,328 @@ -// TODO(@yejiayu): Modify access visibility to private. -pub mod adapter; -pub mod fixed_types; -// mod native_contract; -pub mod trie; +#![feature(trait_alias)] + +mod adapter; +mod cycles; +mod fixed_types; +mod native_contract; +#[cfg(test)] +mod tests; +mod trie; + +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; + +use bytes::Bytes; + +use protocol::traits::executor::contract::{AccountContract, BankContract, ContractStateAdapter}; +use protocol::traits::executor::{Executor, ExecutorExecResp, InvokeContext, RcInvokeContext}; +use protocol::types::{ + Address, Balance, Bloom, ContractAddress, ContractType, Fee, Hash, MerkleRoot, Receipt, + ReceiptResult, SignedTransaction, TransactionAction, UserAddress, +}; +use protocol::ProtocolResult; + +use crate::adapter::{GeneralContractStateAdapter, RcGeneralContractStateAdapter}; +use crate::native_contract::{ + NativeAccountContract, NativeBankContract, ACCOUNT_CONTRACT_ADDRESS, BANK_CONTRACT_ADDRESS, +}; +use crate::trie::{MPTTrie, TrieDB}; + +pub struct TransactionExecutor { + chain_id: Hash, + + trie: MPTTrie, + account_contract: NativeAccountContract>, + bank_account: NativeBankContract>, + state_adapter_map: HashMap>, +} + +impl Executor for TransactionExecutor { + fn exec( + &mut self, + epoch_id: u64, + cycles_price: u64, + coinbase: Address, + signed_txs: Vec, + ) -> ProtocolResult { + let mut receipts = Vec::with_capacity(signed_txs.len()); + + for signed_tx in signed_txs.into_iter() { + let tx_hash = signed_tx.tx_hash.clone(); + + let ictx = gen_invoke_ctx( + epoch_id, + cycles_price, + &self.chain_id, + &coinbase, + &signed_tx, + )?; + + let res = match self.dispatch(Rc::clone(&ictx), signed_tx) { + Ok(res) => { + self.stash()?; + res + } + Err(e) => { + self.revert()?; + ReceiptResult::Fail { + system: e.to_string(), + user: "".to_owned(), + } + } + }; + + self.account_contract.inc_nonce(Rc::clone(&ictx))?; + + let receipt = Receipt { + state_root: Hash::from_empty(), + epoch_id: ictx.borrow().epoch_id, + cycles_used: ictx.borrow().cycles_used.clone(), + result: res, + tx_hash, + }; + receipts.push(receipt); + } + + // Calculate the total fee and reward `coinbsae` + let mut all_cycles_used: Vec = vec![]; + for receipt in receipts.iter() { + modify_all_cycles_used(&mut all_cycles_used, &receipt.cycles_used); + } + for cycles_used in all_cycles_used.iter() { + self.account_contract.add_balance( + &cycles_used.asset_id, + &coinbase, + Balance::from(cycles_used.cycle), + )?; + } + + // commit state + let state_root = self.commit()?; + for receipt in receipts.iter_mut() { + receipt.state_root = state_root.clone(); + } + + Ok(ExecutorExecResp { + receipts, + all_cycles_used, + logs_bloom: Bloom::default(), + state_root: state_root.clone(), + }) + } +} + +impl TransactionExecutor { + pub fn new(chain_id: Hash, db: Arc) -> ProtocolResult { + let mut trie = MPTTrie::new(Arc::clone(&db)); + let root = trie.commit()?; + + Self::from(chain_id, root, Arc::clone(&db)) + } + + pub fn from(chain_id: Hash, state_root: MerkleRoot, db: Arc) -> ProtocolResult { + let trie = MPTTrie::from(state_root.clone(), Arc::clone(&db))?; + + let mut state_adapter_map = HashMap::new(); + + // gen account contract + let account_state_adapter = + gen_contract_state(&trie, &ACCOUNT_CONTRACT_ADDRESS, Arc::clone(&db))?; + let account_contract = NativeAccountContract::new(Rc::clone(&account_state_adapter)); + state_adapter_map.insert( + ACCOUNT_CONTRACT_ADDRESS.clone(), + Rc::clone(&account_state_adapter), + ); + + // gen bank contract + let bank_state_adapter = + gen_contract_state(&trie, &BANK_CONTRACT_ADDRESS, Arc::clone(&db))?; + let bank_account = + NativeBankContract::new(chain_id.clone(), Rc::clone(&bank_state_adapter)); + state_adapter_map.insert( + BANK_CONTRACT_ADDRESS.clone(), + Rc::clone(&bank_state_adapter), + ); + + Ok(Self { + chain_id, + trie, + account_contract, + bank_account, + state_adapter_map, + }) + } + + fn dispatch( + &mut self, + ictx: RcInvokeContext, + signed_tx: SignedTransaction, + ) -> ProtocolResult { + let action = &signed_tx.raw.action; + + let res = match action { + TransactionAction::Transfer { receiver, .. } => { + let to = &Address::User(receiver.clone()); + self.handle_transfer(Rc::clone(&ictx), &to)? + } + TransactionAction::Deploy { + code, + contract_type, + } => self.handle_deploy(Rc::clone(&ictx), code, contract_type)?, + _ => panic!("Unsupported transaction"), + }; + + Ok(res) + } + + fn handle_transfer( + &mut self, + ictx: RcInvokeContext, + to: &Address, + ) -> ProtocolResult { + let from = &ictx.borrow().caller; + let carrying_asset = ictx + .borrow() + .carrying_asset + .clone() + .expect("in transfer, `carrying_asset` cannot be empty"); + + // check asset exists + self.bank_account + .get_asset(Rc::clone(&ictx), &carrying_asset.asset_id)?; + + let before_amount = self + .account_contract + .get_balance(&carrying_asset.asset_id, &from)?; + + self.account_contract.transfer(Rc::clone(&ictx), &to)?; + + let after_amount = self + .account_contract + .get_balance(&carrying_asset.asset_id, &from)?; + + Ok(ReceiptResult::Transfer { + receiver: UserAddress::from_bytes(to.as_bytes())?, + asset_id: carrying_asset.asset_id.clone(), + before_amount, + after_amount, + }) + } + + fn handle_deploy( + &mut self, + ictx: RcInvokeContext, + code: &Bytes, + contract_type: &ContractType, + ) -> ProtocolResult { + match contract_type { + ContractType::Asset => { + // TODO(@yejiayu): Check account balance? + let nonce = self.account_contract.get_nonce(&ictx.borrow().caller)?; + let address = ContractAddress::from_code(code.clone(), nonce, ContractType::Asset)?; + + self.bank_account.register( + Rc::clone(&ictx), + &address, + "muta-token".to_owned(), + "MTT".to_owned(), + Balance::from(21_000_000u64), + )?; + + Ok(ReceiptResult::Deploy { + contract: address, + contract_type: ContractType::Asset, + }) + } + _ => panic!("Unsupported transaction"), + } + } + + fn stash(&mut self) -> ProtocolResult<()> { + for (_, state) in self.state_adapter_map.iter() { + state.borrow_mut().stash()?; + } + Ok(()) + } + + fn revert(&mut self) -> ProtocolResult<()> { + for (_, state) in self.state_adapter_map.iter() { + state.borrow_mut().revert_cache()?; + } + Ok(()) + } + + fn commit(&mut self) -> ProtocolResult { + for (address, state) in self.state_adapter_map.iter() { + let root = state.borrow_mut().commit()?; + + self.trie.insert(address.as_bytes(), root.as_bytes())?; + } + + self.trie.commit() + } +} + +fn gen_contract_state( + trie: &MPTTrie, + address: &Address, + db: Arc, +) -> ProtocolResult> { + let trie = { + if let Some(val) = trie.get(&address.as_bytes())? { + let contract_root = MerkleRoot::from_bytes(val)?; + MPTTrie::from(contract_root, db)? + } else { + MPTTrie::new(db) + } + }; + + let state_adapter = GeneralContractStateAdapter::new(trie); + Ok(Rc::new(RefCell::new(state_adapter))) +} + +fn modify_all_cycles_used(all_cycles_used: &mut Vec, cycles_used: &Fee) { + for fee in all_cycles_used.iter_mut() { + if fee.asset_id == cycles_used.asset_id { + fee.cycle += cycles_used.cycle; + return; + } + } + + let new_fee = Fee { + asset_id: cycles_used.asset_id.clone(), + cycle: cycles_used.cycle, + }; + + all_cycles_used.push(new_fee); +} + +fn gen_invoke_ctx( + epoch_id: u64, + cycles_price: u64, + chain_id: &Hash, + coinbase: &Address, + signed_tx: &SignedTransaction, +) -> ProtocolResult { + let carrying_asset = match &signed_tx.raw.action { + TransactionAction::Transfer { carrying_asset, .. } => Some(carrying_asset.clone()), + TransactionAction::Call { carrying_asset, .. } => carrying_asset.clone(), + _ => None, + }; + + let ctx = InvokeContext { + chain_id: chain_id.clone(), + cycles_used: Fee { + asset_id: signed_tx.raw.fee.asset_id.clone(), + cycle: 0, + }, + cycles_limit: signed_tx.raw.fee.clone(), + caller: Address::User(UserAddress::from_pubkey_bytes(signed_tx.pubkey.clone())?), + coinbase: coinbase.clone(), + epoch_id, + cycles_price, + carrying_asset, + }; + Ok(Rc::new(RefCell::new(ctx))) +} diff --git a/core/executor/src/native_contract/account.rs b/core/executor/src/native_contract/account.rs new file mode 100644 index 000000000..50409f426 --- /dev/null +++ b/core/executor/src/native_contract/account.rs @@ -0,0 +1,306 @@ +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::error::Error; +use std::rc::Rc; + +use derive_more::{Display, From}; + +use protocol::traits::executor::contract::{AccountContract, ContractStateAdapter}; +use protocol::traits::executor::RcInvokeContext; +use protocol::types::{ + Account, Address, AssetID, AssetInfo, Balance, ContractAccount, Hash, UserAccount, +}; +use protocol::{ProtocolError, ProtocolErrorKind, ProtocolResult}; + +use crate::cycles::{consume_cycles, CyclesAction}; +use crate::fixed_types::{FixedAccount, FixedAccountSchema, FixedAddress}; + +pub struct NativeAccountContract { + state_adapter: Rc>, +} + +impl NativeAccountContract { + pub fn new(state_adapter: Rc>) -> Self { + Self { state_adapter } + } +} + +impl AccountContract + for NativeAccountContract +{ + fn transfer(&mut self, ictx: RcInvokeContext, to: &Address) -> ProtocolResult<()> { + let carrying_asset = ictx + .borrow() + .carrying_asset + .clone() + .ok_or(NativeAccountContractError::InsufficientBalance)?; + + self.sub_balance( + &carrying_asset.asset_id, + &ictx.borrow().caller, + carrying_asset.amount.clone(), + )?; + self.add_balance(&carrying_asset.asset_id, to, carrying_asset.amount.clone())?; + + let mut fee = ictx.borrow().cycles_used.clone(); + consume_cycles( + CyclesAction::AccountTransfer, + ictx.borrow().cycles_price, + &mut fee, + &ictx.borrow().cycles_limit, + )?; + ictx.borrow_mut().cycles_used = fee; + Ok(()) + } + + fn add_balance( + &mut self, + id: &AssetID, + address: &Address, + amount: Balance, + ) -> ProtocolResult<()> { + let account = self.find_or_create(address)?; + + let modified_account = match account { + Account::User(mut user) => { + self.add_balance_with_user(id, &mut user, amount)?; + Account::User(user) + } + Account::Contract(mut contract) => { + self.add_balance_with_contract(id, &mut contract, amount)?; + Account::Contract(contract) + } + }; + + self.state_adapter + .borrow_mut() + .insert_cache::( + FixedAddress::new(address.clone()), + FixedAccount::new(modified_account), + )?; + Ok(()) + } + + fn sub_balance( + &mut self, + id: &AssetID, + address: &Address, + amount: Balance, + ) -> ProtocolResult<()> { + let account = self.find_or_create(address)?; + + let modified_account = match account { + Account::User(mut user) => { + self.sub_balance_with_user(id, &mut user, amount)?; + Account::User(user) + } + Account::Contract(mut contract) => { + self.sub_balance_with_contract(id, &mut contract, amount)?; + Account::Contract(contract) + } + }; + + self.state_adapter + .borrow_mut() + .insert_cache::( + FixedAddress::new(address.clone()), + FixedAccount::new(modified_account), + )?; + Ok(()) + } + + fn inc_nonce(&mut self, ictx: RcInvokeContext) -> ProtocolResult<()> { + let caller = &ictx.borrow().caller; + let account = self.get_account(caller)?; + + let modified_account = match account { + Account::User(user) => Account::User(UserAccount { + nonce: user.nonce + 1, + assets: user.assets, + }), + Account::Contract(contract) => Account::Contract(ContractAccount { + nonce: contract.nonce + 1, + assets: contract.assets, + storage_root: contract.storage_root, + }), + }; + + self.state_adapter + .borrow_mut() + .insert_cache::( + FixedAddress::new(caller.clone()), + FixedAccount::new(modified_account), + )?; + Ok(()) + } + + fn get_balance(&self, id: &AssetID, address: &Address) -> ProtocolResult { + let fixed_account = self + .state_adapter + .borrow() + .get::(&FixedAddress::new(address.clone()))? + .ok_or(NativeAccountContractError::AccountNotFound { + address: address.clone(), + })?; + + match fixed_account.inner { + Account::User(user) => self.get_balance_with_user(id, &user), + Account::Contract(contract) => self.get_balance_with_contract(id, &contract), + } + } + + fn get_account(&self, address: &Address) -> ProtocolResult { + let fixed_accoount = self + .state_adapter + .borrow() + .get::(&FixedAddress::new(address.clone()))? + .ok_or(NativeAccountContractError::AccountNotFound { + address: address.clone(), + })?; + Ok(fixed_accoount.inner) + } + + fn get_nonce(&self, address: &Address) -> ProtocolResult { + let account = self.get_account(address)?; + + match account { + Account::User(user) => Ok(user.nonce), + Account::Contract(contract) => Ok(contract.nonce), + } + } +} + +impl NativeAccountContract { + fn find_or_create(&self, address: &Address) -> ProtocolResult { + if let Some(fixed_account) = self + .state_adapter + .borrow() + .get::(&FixedAddress::new(address.clone()))? + { + return Ok(fixed_account.inner); + } + + let account = match address { + Address::User(_) => Account::User(UserAccount { + nonce: 0, + assets: BTreeMap::new(), + }), + Address::Contract(_) => Account::Contract(ContractAccount { + nonce: 0, + assets: BTreeMap::new(), + storage_root: Hash::from_empty(), + }), + }; + Ok(account) + } + + fn get_balance_with_user( + &self, + id: &AssetID, + account: &UserAccount, + ) -> ProtocolResult { + if let Some(info) = account.assets.get(id) { + Ok(info.balance.clone()) + } else { + Ok(Balance::from(0u64)) + } + } + + fn get_balance_with_contract( + &self, + id: &AssetID, + account: &ContractAccount, + ) -> ProtocolResult { + if let Some(balance) = account.assets.get(id) { + Ok(balance.clone()) + } else { + Ok(Balance::from(0u64)) + } + } + + fn add_balance_with_user( + &self, + id: &AssetID, + account: &mut UserAccount, + amount: Balance, + ) -> ProtocolResult<()> { + account + .assets + .entry(id.clone()) + .and_modify(|info| info.balance += amount.clone()) + .or_insert_with(|| AssetInfo { + balance: amount, + approved: BTreeMap::new(), + }); + Ok(()) + } + + fn add_balance_with_contract( + &mut self, + id: &AssetID, + account: &mut ContractAccount, + amount: Balance, + ) -> ProtocolResult<()> { + account + .assets + .entry(id.clone()) + .and_modify(|balance| *balance += amount.clone()) + .or_insert(amount); + Ok(()) + } + + fn sub_balance_with_user( + &mut self, + id: &AssetID, + account: &mut UserAccount, + amount: Balance, + ) -> ProtocolResult<()> { + if let Some(info) = account.assets.get_mut(id) { + if info.balance < amount { + return Err(NativeAccountContractError::InsufficientBalance.into()); + } + + info.balance -= amount; + return Ok(()); + } + + Err(NativeAccountContractError::InsufficientBalance.into()) + } + + fn sub_balance_with_contract( + &self, + id: &AssetID, + account: &mut ContractAccount, + amount: Balance, + ) -> ProtocolResult<()> { + account + .assets + .entry(id.clone()) + .and_modify(|balance| *balance -= amount.clone()) + .or_insert(amount); + Ok(()) + } +} + +#[derive(Debug, Display, From)] +pub enum NativeAccountContractError { + #[display(fmt = "Insufficient balance")] + InsufficientBalance, + + #[display(fmt = "account {:?} not found", address)] + AccountNotFound { address: Address }, + + #[display(fmt = "invalid address")] + InvalidAddress, + + #[display(fmt = "fixed codec {:?}", _0)] + FixedCodec(rlp::DecoderError), +} + +impl Error for NativeAccountContractError {} + +impl From for ProtocolError { + fn from(err: NativeAccountContractError) -> ProtocolError { + ProtocolError::new(ProtocolErrorKind::Executor, Box::new(err)) + } +} diff --git a/core/executor/src/native_contract/bank.rs b/core/executor/src/native_contract/bank.rs new file mode 100644 index 000000000..dfd558b56 --- /dev/null +++ b/core/executor/src/native_contract/bank.rs @@ -0,0 +1,129 @@ +use std::cell::RefCell; +use std::error::Error; +use std::rc::Rc; + +use bytes::Bytes; +use derive_more::{Display, From}; + +use protocol::traits::executor::contract::{BankContract, ContractStateAdapter}; +use protocol::traits::executor::RcInvokeContext; +use protocol::types::{Asset, AssetID, Balance, ContractAddress, ContractType, Hash}; +use protocol::{ProtocolError, ProtocolErrorKind, ProtocolResult}; + +use crate::cycles::{consume_cycles, CyclesAction}; +use crate::fixed_types::{FixedAsset, FixedAssetID, FixedAssetSchema}; + +/// Bank is the registration and query center for asset. +/// +/// It only does two things +/// 1. Responsible for generating a unique ID for the asset and writing the +/// asset's information to the chain. +/// 2. Query the basic information of the asset by asset id. +pub struct NativeBankContract { + chain_id: Hash, + + state_adapter: Rc>, +} + +impl NativeBankContract { + pub fn new(chain_id: Hash, state_adapter: Rc>) -> Self { + Self { + chain_id, + state_adapter, + } + } +} + +impl BankContract + for NativeBankContract +{ + // Register an asset. + // The asset id is generated by: AssetID = Hash(ChainID + AssetContractAddress). + // + // NOTE: After the asset is successfully registered, the `world state` will not + // be modified unless `commit` is called. + fn register( + &mut self, + ictx: RcInvokeContext, + address: &ContractAddress, + name: String, + symbol: String, + supply: Balance, + ) -> ProtocolResult { + if address.contract_type() != ContractType::Asset { + return Err(NativeBankContractError::InvalidAddress.into()); + } + + let asset_id = Hash::digest(Bytes::from( + [self.chain_id.as_bytes(), address.as_bytes()].concat(), + )); + + // Although the probability of a collision is small, we should still check it. + if self + .state_adapter + .borrow() + .contains::(&FixedAssetID::new(asset_id.clone()))? + { + return Err(NativeBankContractError::AssetExists { id: asset_id }.into()); + } + + let asset = Asset { + name, + symbol, + supply, + + id: asset_id.clone(), + manage_contract: address.clone(), + storage_root: Hash::from_empty(), + }; + + self.state_adapter + .borrow_mut() + .insert_cache::( + FixedAssetID::new(asset_id.clone()), + FixedAsset::new(asset.clone()), + )?; + + let mut fee = ictx.borrow().cycles_used.clone(); + consume_cycles( + CyclesAction::BankRegister, + ictx.borrow().cycles_price, + &mut fee, + &ictx.borrow().cycles_limit, + )?; + ictx.borrow_mut().cycles_used = fee; + Ok(asset) + } + + fn get_asset(&self, _ictx: RcInvokeContext, id: &AssetID) -> ProtocolResult { + let fixed_asset: FixedAsset = self + .state_adapter + .borrow() + .get::(&FixedAssetID::new(id.clone()))? + .ok_or(NativeBankContractError::NotFound { id: id.clone() })?; + Ok(fixed_asset.inner) + } +} + +#[derive(Debug, Display, From)] +pub enum NativeBankContractError { + #[display(fmt = "asset id {:?} already exists", id)] + AssetExists { id: AssetID }, + + #[display(fmt = "asset id {:?} not found", id)] + NotFound { id: AssetID }, + + #[display(fmt = "invalid address")] + InvalidAddress, + + #[display(fmt = "fixed codec {:?}", _0)] + FixedCodec(rlp::DecoderError), +} + +impl Error for NativeBankContractError {} + +impl From for ProtocolError { + fn from(err: NativeBankContractError) -> ProtocolError { + ProtocolError::new(ProtocolErrorKind::Executor, Box::new(err)) + } +} diff --git a/core/executor/src/native_contract/mod.rs b/core/executor/src/native_contract/mod.rs new file mode 100644 index 000000000..a10c75cd9 --- /dev/null +++ b/core/executor/src/native_contract/mod.rs @@ -0,0 +1,20 @@ +mod account; +mod bank; + +use lazy_static::lazy_static; + +use protocol::types::Address; + +lazy_static! { + pub static ref ACCOUNT_CONTRACT_ADDRESS: Address = Address::from_hex( + "0x23C000000000000000000000000000000000000001" + ) + .expect("0x23C000000000000000000000000000000000000002 is not a legal native contract address."); + pub static ref BANK_CONTRACT_ADDRESS: Address = Address::from_hex( + "0x230000000000000000000000000000000000000002" + ) + .expect("0x230000000000000000000000000000000000000001 is not a legal native contract address."); +} + +pub use account::{NativeAccountContract, NativeAccountContractError}; +pub use bank::{NativeBankContract, NativeBankContractError}; diff --git a/core/executor/src/tests/account_contract.rs b/core/executor/src/tests/account_contract.rs new file mode 100644 index 000000000..f5b716ac6 --- /dev/null +++ b/core/executor/src/tests/account_contract.rs @@ -0,0 +1,50 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use protocol::traits::executor::contract::AccountContract; +use protocol::types::{Address, AssetID, Balance, CarryingAsset, Fee}; + +use crate::native_contract::NativeAccountContract; +use crate::tests::{create_state_adapter, mock_invoke_context}; + +#[test] +fn test_account_contract() { + let state = Rc::new(RefCell::new(create_state_adapter())); + let mut account = NativeAccountContract::new(state); + + let asset = + AssetID::from_hex("0000000000000000000000000000000000000000000000000000000000000003") + .unwrap(); + let fee_asset = + AssetID::from_hex("0000000000000000000000000000000000000000000000000000000000000004") + .unwrap(); + let user1 = Address::from_hex("100000000000000000000000000000000000000001").unwrap(); + let user2 = Address::from_hex("100000000000000000000000000000000000000002").unwrap(); + account + .add_balance(&asset, &user1, 10000u64.into()) + .unwrap(); + + let cycles_used = Fee { + asset_id: fee_asset.clone(), + cycle: 0, + }; + let cycles_limit = Fee { + asset_id: fee_asset.clone(), + cycle: 1_000_000, + }; + let carrying_asset = CarryingAsset { + asset_id: asset.clone(), + amount: 1000u64.into(), + }; + let ctx = mock_invoke_context( + user1.clone(), + Some(carrying_asset), + cycles_used, + cycles_limit, + ); + account.transfer(Rc::clone(&ctx), &user2).unwrap(); + let user1_balance = account.get_balance(&asset, &user1).unwrap(); + assert_eq!(user1_balance, Balance::from(9000u64)); + let user2_balance = account.get_balance(&asset, &user2).unwrap(); + assert_eq!(user2_balance, Balance::from(1000u64)); +} diff --git a/core/executor/src/tests/bank_contract.rs b/core/executor/src/tests/bank_contract.rs new file mode 100644 index 000000000..05f9fc3c0 --- /dev/null +++ b/core/executor/src/tests/bank_contract.rs @@ -0,0 +1,63 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use protocol::traits::executor::contract::BankContract; +use protocol::traits::executor::InvokeContext; +use protocol::types::{Address, AssetID, Balance, ContractAddress, Fee, Hash}; + +use crate::native_contract::NativeBankContract; +use crate::tests::{create_state_adapter, mock_invoke_context}; + +#[test] +fn test_bank_contract() { + let chain_id = + Hash::from_hex("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let address = ContractAddress::from_hex("200000000000000000000000000000000000000000").unwrap(); + let caller = Address::from_hex("230000000000000000000000000000000000000010").unwrap(); + let state = Rc::new(RefCell::new(create_state_adapter())); + let mut bank = NativeBankContract::new(chain_id, state); + let fee_asset = + AssetID::from_hex("0000000000000000000000000000000000000000000000000000000000000000") + .unwrap(); + let cycles_used = Fee { + asset_id: fee_asset.clone(), + cycle: 0, + }; + let cycles_limit = Fee { + asset_id: fee_asset.clone(), + cycle: 1_000_000, + }; + let ctx = mock_invoke_context(caller, None, cycles_used, cycles_limit); + let name = "Muta token".to_owned(); + let symbol = "MTT".to_owned(); + let supply = Balance::from(1e18 as u64); + let asset = bank + .register( + Rc::>::clone(&ctx), + &address, + name.clone(), + symbol.clone(), + supply.clone(), + ) + .unwrap(); + assert_eq!(&asset.symbol, &symbol); + assert_eq!(&asset.name, &name); + assert_eq!(&asset.supply, &supply); + assert_eq!(&asset.manage_contract, &address); + + // use the same address to register + let asset2 = bank.register( + Rc::>::clone(&ctx), + &address, + name.clone(), + symbol.clone(), + supply.clone(), + ); + assert_eq!(asset2.is_err(), true); + + // get asset + let asset_get = bank + .get_asset(Rc::>::clone(&ctx), &asset.id) + .unwrap(); + assert_eq!(&asset, &asset_get); +} diff --git a/core/executor/src/tests/general_state_adapter.rs b/core/executor/src/tests/general_state_adapter.rs new file mode 100644 index 000000000..a533e31f0 --- /dev/null +++ b/core/executor/src/tests/general_state_adapter.rs @@ -0,0 +1,81 @@ +use std::sync::Arc; + +use bytes::Bytes; + +use protocol::traits::executor::contract::ContractStateAdapter; +use protocol::traits::executor::{ContractSchema, ContractSer}; +use protocol::ProtocolResult; + +use crate::adapter::GeneralContractStateAdapter; +use crate::tests; + +struct FixedTestSchema; +impl ContractSchema for FixedTestSchema { + type Key = FixedTestBytes; + type Value = FixedTestBytes; +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct FixedTestBytes { + inner: Bytes, +} + +impl FixedTestBytes { + pub fn new(inner: Bytes) -> Self { + Self { inner } + } +} + +impl ContractSer for FixedTestBytes { + fn encode(&self) -> ProtocolResult { + Ok(self.inner.clone()) + } + + fn decode(bytes: Bytes) -> ProtocolResult { + Ok(FixedTestBytes { inner: bytes }) + } +} + +#[test] +fn insert() { + let memdb = tests::create_empty_memdb(); + let trie = tests::create_empty_trie(Arc::clone(&memdb)); + let mut state_adapter = GeneralContractStateAdapter::new(trie); + + let key = FixedTestBytes::new(Bytes::from(b"test-key".to_vec())); + let value = FixedTestBytes::new(Bytes::from(b"test-value".to_vec())); + state_adapter + .insert_cache::(key.clone(), value.clone()) + .unwrap(); + + let get_value = state_adapter.get::(&key).unwrap().unwrap(); + assert_eq!(get_value, value.clone()); + state_adapter.stash().unwrap(); + + let get_value = state_adapter.get::(&key).unwrap().unwrap(); + assert_eq!(get_value, value.clone()); + + state_adapter.commit().unwrap(); + let get_value = state_adapter.get::(&key).unwrap().unwrap(); + assert_eq!(get_value, value.clone()); +} + +#[test] +fn revert() { + let memdb = tests::create_empty_memdb(); + let trie = tests::create_empty_trie(Arc::clone(&memdb)); + let mut state_adapter = GeneralContractStateAdapter::new(trie); + + let key = FixedTestBytes::new(Bytes::from(b"test-key".to_vec())); + let value = FixedTestBytes::new(Bytes::from(b"test-value".to_vec())); + state_adapter + .insert_cache::(key.clone(), value.clone()) + .unwrap(); + + let get_value = state_adapter.get::(&key).unwrap().unwrap(); + assert_eq!(get_value, value.clone()); + + state_adapter.revert_cache().unwrap(); + let get_value = state_adapter.get::(&key).unwrap(); + assert_eq!(get_value, None); +} diff --git a/core/executor/src/tests/mod.rs b/core/executor/src/tests/mod.rs new file mode 100644 index 000000000..0788de2a3 --- /dev/null +++ b/core/executor/src/tests/mod.rs @@ -0,0 +1,54 @@ +mod account_contract; +mod bank_contract; +mod general_state_adapter; +mod trie; + +use std::cell::RefCell; +use std::rc::Rc; +use std::sync::Arc; + +use protocol::traits::executor::{InvokeContext, RcInvokeContext}; +use protocol::types::{Address, CarryingAsset, Fee, Hash, MerkleRoot}; + +use crate::adapter::GeneralContractStateAdapter; +use crate::trie::MPTTrie; + +type MemTrie = MPTTrie; + +fn create_empty_memdb() -> Arc { + Arc::new(cita_trie::MemoryDB::new(false)) +} + +fn create_empty_trie(db: Arc) -> MemTrie { + MemTrie::new(Arc::clone(&db)) +} + +fn create_trie_from_root(root: MerkleRoot, db: Arc) -> MemTrie { + MemTrie::from(root, Arc::clone(&db)).unwrap() +} + +fn create_state_adapter() -> GeneralContractStateAdapter { + let memdb = create_empty_memdb(); + let trie = create_empty_trie(Arc::clone(&memdb)); + GeneralContractStateAdapter::new(trie) +} + +fn mock_invoke_context( + caller: Address, + carrying_asset: Option, + cycles_used: Fee, + cycles_limit: Fee, +) -> RcInvokeContext { + let ictx = InvokeContext { + chain_id: Hash::from_empty(), + cycles_price: 1, + epoch_id: 1, + coinbase: caller.clone(), + caller, + cycles_used, + cycles_limit, + carrying_asset, + }; + + Rc::new(RefCell::new(ictx)) +} diff --git a/core/executor/src/tests/trie.rs b/core/executor/src/tests/trie.rs new file mode 100644 index 000000000..a84eda029 --- /dev/null +++ b/core/executor/src/tests/trie.rs @@ -0,0 +1,62 @@ +use std::sync::Arc; + +use bytes::Bytes; + +use crate::tests; + +#[test] +fn insert() { + let memdb = tests::create_empty_memdb(); + let mut trie = tests::create_empty_trie(Arc::clone(&memdb)); + + let key = Bytes::from(b"test-key".to_vec()); + let value = Bytes::from(b"test-value".to_vec()); + + trie.insert(key.clone(), value.clone()).unwrap(); + let root = trie.commit().unwrap(); + let get_value = trie.get(&key).unwrap().unwrap(); + assert_eq!(value, get_value); + + let new_trie = tests::create_trie_from_root(root, Arc::clone(&memdb)); + let get_value = new_trie.get(&key).unwrap().unwrap(); + assert_eq!(value, get_value) +} + +#[test] +fn contains() { + let memdb = tests::create_empty_memdb(); + let mut trie = tests::create_empty_trie(Arc::clone(&memdb)); + + let key = Bytes::from(b"test-key".to_vec()); + let value = Bytes::from(b"test-value".to_vec()); + + trie.insert(key.clone(), value.clone()).unwrap(); + assert_eq!(trie.contains(&key).unwrap(), true); + let root = trie.commit().unwrap(); + + let new_trie = tests::create_trie_from_root(root, Arc::clone(&memdb)); + assert_eq!(new_trie.contains(&key).unwrap(), true) +} + +#[test] +fn commit() { + let memdb = tests::create_empty_memdb(); + let mut trie = tests::create_empty_trie(Arc::clone(&memdb)); + + let key = Bytes::from(b"test-key".to_vec()); + let value = Bytes::from(b"test-value".to_vec()); + + trie.insert(key.clone(), value.clone()).unwrap(); + let root = trie.commit().unwrap(); + + let mut new_trie = tests::create_trie_from_root(root.clone(), Arc::clone(&memdb)); + let root2 = new_trie.commit().unwrap(); + assert_eq!(root.clone(), root2.clone()); + + let key2 = Bytes::from(b"test-key2".to_vec()); + let value2 = Bytes::from(b"test-value2".to_vec()); + new_trie.insert(key2, value2).unwrap(); + let root3 = new_trie.commit().unwrap(); + + assert_eq!(root2 != root3, true) +} diff --git a/core/executor/src/trie/mod.rs b/core/executor/src/trie/mod.rs index 3e030c6e3..b9626cbff 100644 --- a/core/executor/src/trie/mod.rs +++ b/core/executor/src/trie/mod.rs @@ -14,13 +14,15 @@ lazy_static! { static ref HASHER_INST: Arc = Arc::new(HasherKeccak::new()); } -pub struct MPTTrie { +pub trait TrieDB = cita_trie::DB; + +pub struct MPTTrie { root: MerkleRoot, - trie: PatriciaTrie, + trie: PatriciaTrie, } -impl MPTTrie { - pub fn new(db: Arc) -> Self { +impl MPTTrie { + pub fn new(db: Arc) -> Self { let trie = PatriciaTrie::new(db, Arc::clone(&HASHER_INST)); Self { @@ -29,23 +31,23 @@ impl MPTTrie { } } - pub fn from(root: MerkleRoot, db: Arc) -> ProtocolResult { + pub fn from(root: MerkleRoot, db: Arc) -> ProtocolResult { let trie = PatriciaTrie::from(db, Arc::clone(&HASHER_INST), &root.as_bytes()) .map_err(MPTTrieError::from)?; Ok(Self { root, trie }) } - pub fn get(&self, key: Bytes) -> ProtocolResult> { + pub fn get(&self, key: &Bytes) -> ProtocolResult> { Ok(self .trie - .get(&key) + .get(key) .map_err(MPTTrieError::from)? .map(Bytes::from)) } - pub fn contains(&self, key: Bytes) -> ProtocolResult { - Ok(self.trie.contains(&key).map_err(MPTTrieError::from)?) + pub fn contains(&self, key: &Bytes) -> ProtocolResult { + Ok(self.trie.contains(key).map_err(MPTTrieError::from)?) } pub fn insert(&mut self, key: Bytes, value: Bytes) -> ProtocolResult<()> { diff --git a/core/executor/src/types/contract.rs b/core/executor/src/types/contract.rs new file mode 100644 index 000000000..2806cbfb1 --- /dev/null +++ b/core/executor/src/types/contract.rs @@ -0,0 +1,423 @@ +use std::collections::BTreeMap; +use std::error::Error; +use std::mem; + +use bytes::Bytes; +use derive_more::{Display, From}; + +use protocol::traits::executor::{ContractSchema, ContractSer}; +use protocol::types::{ + Account, Address, ApprovedInfo, Asset, AssetID, AssetInfo, Balance, ContractAccount, + ContractAddress, Hash, MerkleRoot, UserAccount, +}; +use protocol::{ProtocolError, ProtocolErrorKind, ProtocolResult}; + +pub struct FixedAssetSchema; +impl ContractSchema for FixedAssetSchema { + type Key = FixedAssetID; + type Value = FixedAsset; +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct FixedAssetID { + inner: AssetID, +} + +impl FixedAssetID { + pub fn new(inner: AssetID) -> Self { + Self { inner } + } +} + +impl ContractSer for FixedAssetID { + fn encode(&self) -> ProtocolResult { + Ok(self.inner.as_bytes()) + } + + fn decode(bytes: Bytes) -> ProtocolResult { + let id = AssetID::from_bytes(bytes)?; + Ok(FixedAssetID { inner: id }) + } +} + +/// the `FixedAsset` is a wrapper type of asset just to provide a consistent +/// serialization algorithm `rlp`. +#[derive(Clone, Debug)] +pub struct FixedAsset { + pub inner: Asset, +} + +impl FixedAsset { + pub fn new(inner: Asset) -> Self { + Self { inner } + } +} + +impl ContractSer for FixedAsset { + fn encode(&self) -> ProtocolResult { + Ok(Bytes::from(rlp::encode(self))) + } + + fn decode(bytes: Bytes) -> ProtocolResult { + Ok(rlp::decode(bytes.as_ref()).map_err(FixedTypesError::from)?) + } +} + +/// FixedAsset encodable to RLP +impl rlp::Encodable for FixedAsset { + /// Append a value to the stream + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let inner = &self.inner; + + s.begin_list(6); + s.append(&inner.id.as_bytes().to_vec()); + s.append(&inner.manage_contract.as_bytes().to_vec()); + s.append(&inner.name.as_bytes()); + s.append(&inner.storage_root.as_bytes().to_vec()); + s.append(&inner.supply.to_bytes_be()); + s.append(&inner.symbol.as_bytes()); + } +} + +/// RLP decodable trait +impl rlp::Decodable for FixedAsset { + /// Decode a value from RLP bytes + fn decode(r: &rlp::Rlp) -> Result { + if !r.is_list() && r.size() != 6 { + return Err(rlp::DecoderError::RlpInvalidLength); + } + + let mut values = Vec::with_capacity(6); + + for val in r { + let data = val.data()?; + values.push(data) + } + + let id = Hash::from_bytes(Bytes::from(values[0])) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + let manage_contract = ContractAddress::from_bytes(Bytes::from(values[1])) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + let name = String::from_utf8(values[2].to_vec()) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + let storage_root = Hash::from_bytes(Bytes::from(values[3])) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + let supply = Balance::from_bytes_be(values[4]); + let symbol = String::from_utf8(values[5].to_vec()) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + + let asset = Asset { + id, + manage_contract, + name, + storage_root, + supply, + symbol, + }; + + Ok(FixedAsset { inner: asset }) + } +} + +pub struct FixedAccountSchema; +impl ContractSchema for FixedAccountSchema { + type Key = FixedAddress; + type Value = FixedAccount; +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct FixedAddress { + inner: Address, +} + +impl FixedAddress { + pub fn new(inner: Address) -> Self { + Self { inner } + } +} + +impl ContractSer for FixedAddress { + fn encode(&self) -> ProtocolResult { + match &self.inner { + Address::User(user) => Ok(user.as_bytes()), + Address::Contract(contract) => Ok(contract.as_bytes()), + } + } + + fn decode(bytes: Bytes) -> ProtocolResult { + let address = Address::from_bytes(bytes)?; + Ok(FixedAddress { inner: address }) + } +} + +const USER_ACCOUNT_FLAG: u8 = 0; +const CONTRACT_ACCOUNT_FLAG: u8 = 1; + +/// the `FixedAccount` is a wrapper type of asset just to provide a consistent +/// serialization algorithm `rlp`. +#[derive(Clone, Debug)] +pub struct FixedAccount { + pub inner: Account, +} + +impl FixedAccount { + pub fn new(inner: Account) -> Self { + Self { inner } + } +} + +impl ContractSer for FixedAccount { + fn encode(&self) -> ProtocolResult { + Ok(Bytes::from(rlp::encode(self))) + } + + fn decode(bytes: Bytes) -> ProtocolResult { + Ok(rlp::decode(bytes.as_ref()).map_err(FixedTypesError::from)?) + } +} + +/// FixedAsset encodable to RLP +impl rlp::Encodable for FixedAccount { + /// Append a value to the stream + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let inner = &self.inner; + + match inner { + Account::User(user) => { + s.begin_list(3); + s.append(&USER_ACCOUNT_FLAG); + s.append(&user.nonce.to_be_bytes()); + + let mut asset_list = Vec::with_capacity(user.assets.len()); + + for (id, asset_info) in user.assets.iter() { + let asset_info = FixedUserAssetInfo { + id: id.clone(), + balance: asset_info.balance.clone(), + approved: asset_info.approved.clone(), + }; + + asset_list.push(asset_info); + } + + s.append_list(&asset_list); + } + Account::Contract(contract) => { + s.begin_list(4); + s.append(&CONTRACT_ACCOUNT_FLAG); + s.append(&contract.nonce.to_be_bytes()); + s.append(&contract.storage_root.as_bytes().to_vec()); + + let mut asset_list = Vec::with_capacity(contract.assets.len()); + + for (id, balance) in contract.assets.iter() { + let asset = FixedContractAsset { + id: id.clone(), + balance: balance.clone(), + }; + + asset_list.push(asset); + } + + s.append_list(&asset_list); + } + } + } +} + +/// RLP decodable trait +impl rlp::Decodable for FixedAccount { + /// Decode a value from RLP bytes + fn decode(r: &rlp::Rlp) -> Result { + let flag: u8 = r.at(0)?.as_val()?; + + match flag { + USER_ACCOUNT_FLAG => { + let nonce = bytes_to_u64(r.at(1)?.data())?; + let asset_list: Vec = rlp::decode_list(r.at(2)?.as_raw()); + + let mut assets = BTreeMap::new(); + + for v in asset_list.into_iter() { + assets.insert(v.id, AssetInfo { + balance: v.balance, + approved: v.approved, + }); + } + + Ok(FixedAccount { + inner: Account::User(UserAccount { nonce, assets }), + }) + } + CONTRACT_ACCOUNT_FLAG => { + let nonce: u64 = r.at(1)?.as_val()?; + let storage_root_bytes = r.at(2)?.data()?; + let asset_list: Vec = rlp::decode_list(r.at(3)?.as_raw()); + + let mut assets = BTreeMap::new(); + + for v in asset_list { + assets.insert(v.id, v.balance); + } + + let storage_root = MerkleRoot::from_bytes(Bytes::from(storage_root_bytes)) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?; + + Ok(FixedAccount { + inner: Account::Contract(ContractAccount { + nonce, + assets, + storage_root, + }), + }) + } + _ => Err(rlp::DecoderError::RlpListLenWithZeroPrefix), + } + } +} + +/// the `FixedUserAssetInfo` is a wrapper type of asset just to provide a +/// consistent serialization algorithm `rlp`. +#[derive(Clone, Debug)] +pub struct FixedUserAssetInfo { + pub id: AssetID, + pub balance: Balance, + pub approved: BTreeMap, +} + +/// FixedAsset encodable to RLP +impl rlp::Encodable for FixedUserAssetInfo { + /// Append a value to the stream + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(3); + s.append(&self.id.as_bytes().to_vec()); + s.append(&self.balance.to_bytes_be()); + + let mut info_list = Vec::with_capacity(self.approved.len()); + + for (address, info) in self.approved.iter() { + let fixed_info = FixedUserAssetApproved { + contract_address: address.clone(), + max: info.max.clone(), + used: info.used.clone(), + }; + info_list.push(fixed_info); + } + + s.append_list(&info_list); + } +} + +/// RLP decodable trait +impl rlp::Decodable for FixedUserAssetInfo { + /// Decode a value from RLP bytes + fn decode(r: &rlp::Rlp) -> Result { + let id_bytes = r.at(0)?.data()?; + let balance_bytes = r.at(1)?.data()?; + let approved_list: Vec = rlp::decode_list(r.at(2)?.as_raw()); + + let mut approved_map = BTreeMap::new(); + for v in approved_list { + approved_map.insert(v.contract_address, ApprovedInfo { + max: v.max, + used: v.used, + }); + } + + Ok(FixedUserAssetInfo { + id: AssetID::from_bytes(Bytes::from(id_bytes)) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?, + balance: Balance::from_bytes_be(balance_bytes), + approved: approved_map, + }) + } +} + +/// the `FixedUserAssetApproved` is a wrapper type of asset just to provide a +/// consistent serialization algorithm `rlp`. +#[derive(Clone, Debug)] +pub struct FixedUserAssetApproved { + pub contract_address: ContractAddress, + pub max: Balance, + pub used: Balance, +} + +/// FixedAsset encodable to RLP +impl rlp::Encodable for FixedUserAssetApproved { + /// Append a value to the stream + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(3); + s.append(&self.contract_address.as_bytes().to_vec()); + s.append(&self.max.to_bytes_be()); + s.append(&self.used.to_bytes_be()); + } +} + +/// RLP decodable trait +impl rlp::Decodable for FixedUserAssetApproved { + /// Decode a value from RLP bytes + fn decode(r: &rlp::Rlp) -> Result { + let address_bytes = r.at(0)?.data()?; + let max_bytes = r.at(1)?.data()?; + let used_bytes = r.at(2)?.data()?; + + Ok(FixedUserAssetApproved { + contract_address: ContractAddress::from_bytes(Bytes::from(address_bytes)) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?, + max: Balance::from_bytes_be(max_bytes), + used: Balance::from_bytes_be(used_bytes), + }) + } +} + +/// the `FixedUserAssetApproved` is a wrapper type of asset just to provide a +/// consistent serialization algorithm `rlp`. +#[derive(Clone, Debug)] +pub struct FixedContractAsset { + pub id: AssetID, + pub balance: Balance, +} + +/// FixedAsset encodable to RLP +impl rlp::Encodable for FixedContractAsset { + /// Append a value to the stream + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(2); + s.append(&self.id.as_bytes().to_vec()); + s.append(&self.balance.to_bytes_be()); + } +} + +/// RLP decodable trait +impl rlp::Decodable for FixedContractAsset { + /// Decode a value from RLP bytes + fn decode(r: &rlp::Rlp) -> Result { + let id_bytes = r.at(0)?.data()?; + let balance_bytes = r.at(1)?.data()?; + + Ok(FixedContractAsset { + id: Hash::from_bytes(Bytes::from(id_bytes)) + .map_err(|_| rlp::DecoderError::RlpInvalidLength)?, + balance: Balance::from_bytes_be(balance_bytes), + }) + } +} + +fn bytes_to_u64(bytes: &[u8]) -> u64 { + let mut nonce_bytes = [0u8; 8]; + nonce_bytes.copy_from_slice(bytes); + u64::from_be_bytes(nonce_bytes) +} + +#[derive(Debug, Display, From)] +pub enum FixedTypesError { + Decoder(rlp::DecoderError), +} + +impl Error for FixedTypesError {} + +impl From for ProtocolError { + fn from(err: FixedTypesError) -> ProtocolError { + ProtocolError::new(ProtocolErrorKind::Executor, Box::new(err)) + } +} diff --git a/core/executor/src/types/invoke.rs b/core/executor/src/types/invoke.rs new file mode 100644 index 000000000..b0bbe347a --- /dev/null +++ b/core/executor/src/types/invoke.rs @@ -0,0 +1,15 @@ +use protocol::types::{AssetID, Balance, Address}; + +pub struct FixedTransferRequestSchema; + +impl ContractSchema for FixedTransferRequestSchema { + type Key = FixedTransferArgSchema; + type Value = (); +} + +#[derive(Clone, Debug)] +pub struct FixedTransferArgSchema { + pub id: AssetID, + pub to: Address, + pub amount: Balance, +} diff --git a/core/executor/src/types/mod.rs b/core/executor/src/types/mod.rs new file mode 100644 index 000000000..0da59eafe --- /dev/null +++ b/core/executor/src/types/mod.rs @@ -0,0 +1,2 @@ +pub mod invoke; +pub mod contract; diff --git a/core/mempool/src/adapter/rlp_types.rs b/core/mempool/src/adapter/rlp_types.rs index d71aba711..a2b2e2966 100644 --- a/core/mempool/src/adapter/rlp_types.rs +++ b/core/mempool/src/adapter/rlp_types.rs @@ -2,8 +2,8 @@ use std::iter::once; use bytes::Bytes; use protocol::types::{ - Balance, ContractAddress, ContractType, Fee, Hash, RawTransaction, SignedTransaction, - TransactionAction, UserAddress, + Balance, CarryingAsset, ContractAddress, ContractType, Fee, Hash, RawTransaction, + SignedTransaction, TransactionAction, UserAddress, }; use rlp::{Encodable, RlpStream}; @@ -66,6 +66,16 @@ impl<'a> Encodable for RlpFee<'a> { } } +pub struct RlpCarryingAsset<'a>(&'a CarryingAsset); + +impl<'a> Encodable for RlpCarryingAsset<'a> { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(3); + s.append(&RlpHash(&self.0.asset_id)); + s.append(&RlpBalance(&self.0.amount)); + } +} + pub struct RlpTransfer<'a> { receiver: RlpUserAddress<'a>, asset_id: RlpAssetID<'a>, @@ -122,21 +132,29 @@ impl<'a> Encodable for RlpArgs<'a> { } pub struct RlpCall<'a> { - contract: RlpContractAddress<'a>, - method: &'a str, - args: RlpArgs<'a>, - asset_id: RlpAssetID<'a>, - amount: RlpBalance<'a>, + contract: RlpContractAddress<'a>, + method: &'a str, + args: RlpArgs<'a>, + carrying_asset: Option>, } impl<'a> Encodable for RlpCall<'a> { fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(5); + s.begin_list(4); s.append(&self.contract); s.append(&self.method); s.append(&self.args); - s.append(&self.asset_id); - s.append(&self.amount); + match &self.carrying_asset { + Some(carrying_asset) => { + s.begin_list(3); + s.append(&true); + s.append(carrying_asset); + } + None => { + s.begin_list(1); + s.append(&false); + } + } } } @@ -163,12 +181,11 @@ impl<'a> From<&'a TransactionAction> for RlpTransactionAction<'a> { match tx_act { TransactionAction::Transfer { receiver, - asset_id, - amount, + carrying_asset, } => RlpTransactionAction::Transfer(RlpTransfer { receiver: RlpUserAddress(receiver), - asset_id: RlpHash(&asset_id), - amount: RlpBalance(amount), + asset_id: RlpHash(&carrying_asset.asset_id), + amount: RlpBalance(&carrying_asset.amount), }), TransactionAction::Approve { spender, @@ -190,14 +207,12 @@ impl<'a> From<&'a TransactionAction> for RlpTransactionAction<'a> { contract, method, args, - asset_id, - amount, + carrying_asset, } => RlpTransactionAction::Call(RlpCall { contract: RlpContractAddress(contract), method, args: RlpArgs(args), - asset_id: RlpHash(&asset_id), - amount: RlpBalance(amount), + carrying_asset: carrying_asset.as_ref().map(|a| RlpCarryingAsset(a)), }), } } diff --git a/core/mempool/src/tests/mod.rs b/core/mempool/src/tests/mod.rs index 61d7a0318..0c70836e1 100644 --- a/core/mempool/src/tests/mod.rs +++ b/core/mempool/src/tests/mod.rs @@ -22,7 +22,8 @@ use common_crypto::{ use protocol::codec::ProtocolCodec; use protocol::traits::{Context, MemPool, MemPoolAdapter, MixedTxHashes}; use protocol::types::{ - Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, UserAddress as Address, + CarryingAsset, Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, + UserAddress as Address, }; use protocol::ProtocolResult; @@ -233,9 +234,11 @@ fn mock_signed_tx( cycle: TX_CYCLE, }; let action = TransactionAction::Transfer { - receiver: address.clone(), - asset_id: nonce.clone(), - amount: FromPrimitive::from_i32(AMOUNT).unwrap(), + receiver: address.clone(), + carrying_asset: CarryingAsset { + asset_id: nonce.clone(), + amount: FromPrimitive::from_i32(AMOUNT).unwrap(), + }, }; let mut raw = RawTransaction { chain_id: nonce.clone(), diff --git a/core/mempool/src/tx_cache.rs b/core/mempool/src/tx_cache.rs index 9bb5ffbbb..0d8213e95 100644 --- a/core/mempool/src/tx_cache.rs +++ b/core/mempool/src/tx_cache.rs @@ -361,7 +361,7 @@ mod tests { use test::Bencher; use protocol::types::{ - Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, UserAddress, + CarryingAsset, Fee, Hash, RawTransaction, SignedTransaction, TransactionAction, UserAddress, }; use crate::tx_cache::TxCache; @@ -401,9 +401,11 @@ mod tests { cycle: TX_CYCLE, }; let action = TransactionAction::Transfer { - receiver: address.clone(), - asset_id, - amount: FromPrimitive::from_i32(10_000).unwrap(), + receiver: address.clone(), + carrying_asset: CarryingAsset { + asset_id, + amount: FromPrimitive::from_i32(10_000).unwrap(), + }, }; let raw = RawTransaction { chain_id, diff --git a/core/storage/src/tests/mod.rs b/core/storage/src/tests/mod.rs index dc12b97a5..c7db0d069 100644 --- a/core/storage/src/tests/mod.rs +++ b/core/storage/src/tests/mod.rs @@ -14,7 +14,7 @@ use num_traits::FromPrimitive; use rand::random; use protocol::types::{ - Epoch, EpochHeader, Fee, Hash, Proof, RawTransaction, Receipt, ReceiptResult, + CarryingAsset, Epoch, EpochHeader, Fee, Hash, Proof, RawTransaction, Receipt, ReceiptResult, SignedTransaction, TransactionAction, UserAddress, }; @@ -27,9 +27,11 @@ fn mock_signed_tx(tx_hash: Hash) -> SignedTransaction { let addr_str = "10CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; let action = TransactionAction::Transfer { - receiver: UserAddress::from_hex(addr_str).unwrap(), - asset_id: nonce.clone(), - amount: FromPrimitive::from_i32(10).unwrap(), + receiver: UserAddress::from_hex(addr_str).unwrap(), + carrying_asset: CarryingAsset { + asset_id: nonce.clone(), + amount: FromPrimitive::from_i32(10).unwrap(), + }, }; let raw = RawTransaction { chain_id: nonce.clone(), @@ -56,6 +58,7 @@ fn mock_receipt(tx_hash: Hash) -> Receipt { let addr_str = "10CAB8EEA4799C21379C20EF5BAA2CC8AF1BEC475B"; let result = ReceiptResult::Transfer { receiver: UserAddress::from_hex(addr_str).unwrap(), + asset_id: nonce.clone(), before_amount: FromPrimitive::from_i32(10).unwrap(), after_amount: FromPrimitive::from_i32(20).unwrap(), }; diff --git a/protocol/src/codec/receipt.rs b/protocol/src/codec/receipt.rs index 5f5b908e6..f04f8f22e 100644 --- a/protocol/src/codec/receipt.rs +++ b/protocol/src/codec/receipt.rs @@ -55,8 +55,10 @@ pub struct Transfer { #[prost(message, tag = "1")] pub receiver: Option, #[prost(message, tag = "2")] - pub before_amount: Option, + pub asset_id: Option, #[prost(message, tag = "3")] + pub before_amount: Option, + #[prost(message, tag = "4")] pub after_amount: Option, } @@ -107,11 +109,13 @@ impl From for ReceiptResult { match result { receipt::ReceiptResult::Transfer { receiver, + asset_id, before_amount, after_amount, } => { let transfer = Transfer { receiver: Some(UserAddress::from(receiver)), + asset_id: Some(AssetID::from(asset_id)), before_amount: Some(Balance::from(before_amount)), after_amount: Some(Balance::from(after_amount)), }; @@ -171,6 +175,7 @@ impl TryFrom for receipt::ReceiptResult { match result { ReceiptResult::Transfer(transfer) => { let receiver = field!(transfer.receiver, "ReceiptResult::Transfer", "receiver")?; + let asset_id = field!(transfer.asset_id, "ReceiptResult::Transfer", "asset_id")?; let before_amount = field!( transfer.before_amount, "ReceiptResult::Transfer", @@ -184,6 +189,7 @@ impl TryFrom for receipt::ReceiptResult { let result = receipt::ReceiptResult::Transfer { receiver: protocol_primitive::UserAddress::try_from(receiver)?, + asset_id: protocol_primitive::AssetID::try_from(asset_id)?, before_amount: protocol_primitive::Balance::try_from(before_amount)?, after_amount: protocol_primitive::Balance::try_from(after_amount)?, }; diff --git a/protocol/src/codec/tests/mod.rs b/protocol/src/codec/tests/mod.rs index d2c9e91a5..6ba66197b 100644 --- a/protocol/src/codec/tests/mod.rs +++ b/protocol/src/codec/tests/mod.rs @@ -12,7 +12,7 @@ use crate::types::{ Asset, AssetID, Balance, ContractAddress, ContractType, Fee, Hash, MerkleRoot, UserAddress, }, receipt::{Receipt, ReceiptResult}, - transaction::{RawTransaction, SignedTransaction, TransactionAction}, + transaction::{CarryingAsset, RawTransaction, SignedTransaction, TransactionAction}, }; enum ReceiptType { @@ -84,6 +84,7 @@ fn mock_result(rtype: ReceiptType) -> ReceiptResult { match rtype { ReceiptType::Transfer => ReceiptResult::Transfer { receiver: mock_account_address(), + asset_id: mock_asset_id(), before_amount: mock_balance(), after_amount: mock_balance(), }, @@ -125,9 +126,11 @@ fn mock_receipt(rtype: ReceiptType) -> Receipt { fn mock_action(atype: AType) -> TransactionAction { match atype { AType::Transfer => TransactionAction::Transfer { - receiver: mock_account_address(), - asset_id: mock_asset_id(), - amount: mock_balance(), + receiver: mock_account_address(), + carrying_asset: CarryingAsset { + asset_id: mock_asset_id(), + amount: mock_balance(), + }, }, AType::Approve => TransactionAction::Approve { spender: mock_contract_address(), @@ -139,11 +142,13 @@ fn mock_action(atype: AType) -> TransactionAction { contract_type: ContractType::Library, }, AType::Call => TransactionAction::Call { - contract: mock_contract_address(), - method: "get".to_string(), - args: vec![get_random_bytes(10), get_random_bytes(10)], - asset_id: mock_asset_id(), - amount: mock_balance(), + contract: mock_contract_address(), + method: "get".to_string(), + args: vec![get_random_bytes(10), get_random_bytes(10)], + carrying_asset: Some(CarryingAsset { + asset_id: mock_asset_id(), + amount: mock_balance(), + }), }, } } diff --git a/protocol/src/codec/transaction.rs b/protocol/src/codec/transaction.rs index 1305d517e..3b24e4086 100644 --- a/protocol/src/codec/transaction.rs +++ b/protocol/src/codec/transaction.rs @@ -13,16 +13,22 @@ use crate::{ ProtocolError, ProtocolResult, }; +#[derive(Clone, Message)] +pub struct CarryingAsset { + #[prost(message, tag = "1")] + pub asset_id: Option, + + #[prost(message, tag = "2")] + pub amount: Option, +} + #[derive(Clone, Message)] pub struct Transfer { #[prost(message, tag = "1")] pub receiver: Option, #[prost(message, tag = "2")] - pub asset_id: Option, - - #[prost(message, tag = "3")] - pub amount: Option, + pub carrying_asset: Option, } #[derive(Clone, Message)] @@ -58,10 +64,7 @@ pub struct Call { pub args: Vec>, #[prost(message, tag = "4")] - pub asset_id: Option, - - #[prost(message, tag = "5")] - pub amount: Option, + pub carrying_asset: Option, } #[derive(Clone, Oneof)] @@ -116,6 +119,30 @@ pub struct SignedTransaction { // Conversion // ################# +// CarryingAsset +impl From for CarryingAsset { + fn from(ca: transaction::CarryingAsset) -> CarryingAsset { + let asset_id = Some(AssetID::from(ca.asset_id)); + let amount = Some(Balance::from(ca.amount)); + + CarryingAsset { asset_id, amount } + } +} + +impl TryFrom for transaction::CarryingAsset { + type Error = ProtocolError; + + fn try_from(ca: CarryingAsset) -> Result { + let asset_id = field!(ca.asset_id, "CarryingAsset", "asset_id")?; + let amount = field!(ca.amount, "CarryingAsset", "amount")?; + + Ok(transaction::CarryingAsset { + asset_id: protocol_primitive::AssetID::try_from(asset_id)?, + amount: protocol_primitive::Balance::try_from(amount)?, + }) + } +} + // TransactionAction impl From for TransactionAction { @@ -123,13 +150,14 @@ impl From for TransactionAction { match action { transaction::TransactionAction::Transfer { receiver, - asset_id, - amount, + carrying_asset, } => { let transfer = Transfer { - receiver: Some(UserAddress::from(receiver)), - asset_id: Some(AssetID::from(asset_id)), - amount: Some(Balance::from(amount)), + receiver: Some(UserAddress::from(receiver)), + carrying_asset: Some(CarryingAsset { + asset_id: Some(AssetID::from(carrying_asset.asset_id)), + amount: Some(Balance::from(carrying_asset.amount)), + }), }; TransactionAction::Transfer(transfer) @@ -162,8 +190,7 @@ impl From for TransactionAction { contract, method, args, - asset_id, - amount, + carrying_asset, } => { let args = args.into_iter().map(|arg| arg.to_vec()).collect::>(); @@ -171,8 +198,16 @@ impl From for TransactionAction { contract: Some(ContractAddress::from(contract)), method, args, - asset_id: Some(AssetID::from(asset_id)), - amount: Some(Balance::from(amount)), + carrying_asset: { + if let Some(ca) = carrying_asset { + Some(CarryingAsset { + asset_id: Some(AssetID::from(ca.asset_id)), + amount: Some(Balance::from(ca.amount)), + }) + } else { + None + } + }, }; TransactionAction::Call(call) @@ -189,14 +224,15 @@ impl TryFrom for transaction::TransactionAction { TransactionAction::Transfer(transfer) => { let receiver = field!(transfer.receiver, "TransactionAction::Transfer", "receiver")?; - let asset_id = - field!(transfer.asset_id, "TransactionAction::Transfer", "asset_id")?; - let amount = field!(transfer.amount, "TransactionAction::Transfer", "amount")?; + let carrying_asset = field!( + transfer.carrying_asset, + "TransactionAction::Transfer", + "carrying_asset" + )?; let action = transaction::TransactionAction::Transfer { - receiver: protocol_primitive::UserAddress::try_from(receiver)?, - asset_id: protocol_primitive::AssetID::try_from(asset_id)?, - amount: protocol_primitive::Balance::try_from(amount)?, + receiver: protocol_primitive::UserAddress::try_from(receiver)?, + carrying_asset: transaction::CarryingAsset::try_from(carrying_asset)?, }; Ok(action) @@ -231,16 +267,20 @@ impl TryFrom for transaction::TransactionAction { } TransactionAction::Call(call) => { let contract = field!(call.contract, "TransactionAction::Call", "contract")?; - let asset_id = field!(call.asset_id, "Transaction::Call", "asset_id")?; - let amount = field!(call.amount, "Transaction::Call", "amount")?; let args = call.args.into_iter(); + let opt_carrying_asset = call.carrying_asset; let action = transaction::TransactionAction::Call { - contract: protocol_primitive::ContractAddress::try_from(contract)?, - method: call.method, - args: args.map(Bytes::from).collect::>(), - asset_id: protocol_primitive::AssetID::try_from(asset_id)?, - amount: protocol_primitive::Balance::try_from(amount)?, + contract: protocol_primitive::ContractAddress::try_from(contract)?, + method: call.method, + args: args.map(Bytes::from).collect::>(), + carrying_asset: { + if let Some(carrying_asset) = opt_carrying_asset { + Some(transaction::CarryingAsset::try_from(carrying_asset)?) + } else { + None + } + }, }; Ok(action) diff --git a/protocol/src/traits/executor/contract.rs b/protocol/src/traits/executor/contract.rs index 7e9846709..37ee42a43 100644 --- a/protocol/src/traits/executor/contract.rs +++ b/protocol/src/traits/executor/contract.rs @@ -1,7 +1,10 @@ -use crate::traits::executor::{ContractSchema, InvokeContext}; -use crate::types::{Address, Asset, AssetID, Balance, ContractAddress, MerkleRoot}; +use crate::traits::executor::{ContractSchema, RcInvokeContext}; +use crate::types::{Account, Address, Asset, AssetID, Balance, ContractAddress, MerkleRoot}; use crate::ProtocolResult; +// As the world state access layer, the ContractStateAdapter provides `cache` +// `stash` and `commit`, and all native-contract that interact with the +// `world state` should rely on it. pub trait ContractStateAdapter { fn get( &self, @@ -10,16 +13,21 @@ pub trait ContractStateAdapter { fn contains(&self, key: &Schema::Key) -> ProtocolResult; + // Insert a pair of data into the cache. fn insert_cache( &mut self, key: Schema::Key, value: Schema::Value, ) -> ProtocolResult<()>; + // Clear cache, called when executor fails. fn revert_cache(&mut self) -> ProtocolResult<()>; + // Put the data in the current cache into the stash space, which means that the + // data will be finilazy when 'commit' is invoked. fn stash(&mut self) -> ProtocolResult<()>; + // Persist the data of stash space to `world state` and empty stash space. fn commit(&mut self) -> ProtocolResult; } @@ -37,30 +45,38 @@ pub trait BankContract { // be modified unless `commit` is called. fn register( &mut self, - ictx: InvokeContext, - address: ContractAddress, + ictx: RcInvokeContext, + address: &ContractAddress, name: String, symbol: String, supply: Balance, ) -> ProtocolResult; - fn get_asset(&self, ictx: InvokeContext, id: &AssetID) -> ProtocolResult; + fn get_asset(&self, ictx: RcInvokeContext, id: &AssetID) -> ProtocolResult; } pub trait AccountContract { - fn transfer( + fn transfer(&mut self, ictx: RcInvokeContext, to: &Address) -> ProtocolResult<()>; + + fn inc_nonce(&mut self, ictx: RcInvokeContext) -> ProtocolResult<()>; + + fn add_balance( &mut self, - ictx: InvokeContext, id: &AssetID, + address: &Address, amount: Balance, - from: &Address, - to: &Address, ) -> ProtocolResult<()>; - fn get_balance( - &self, - ictx: InvokeContext, + fn sub_balance( + &mut self, id: &AssetID, address: &Address, - ) -> ProtocolResult; + amount: Balance, + ) -> ProtocolResult<()>; + + fn get_balance(&self, id: &AssetID, address: &Address) -> ProtocolResult; + + fn get_account(&self, address: &Address) -> ProtocolResult; + + fn get_nonce(&self, address: &Address) -> ProtocolResult; } diff --git a/protocol/src/traits/executor/mod.rs b/protocol/src/traits/executor/mod.rs index 81469bd24..236e547d7 100644 --- a/protocol/src/traits/executor/mod.rs +++ b/protocol/src/traits/executor/mod.rs @@ -1,33 +1,52 @@ pub mod contract; -use async_trait::async_trait; +use std::cell::RefCell; +use std::rc::Rc; + use bytes::Bytes; -use crate::types::{Address, ContractAddress}; +use crate::types::{ + Address, Bloom, CarryingAsset, ContractAddress, Fee, Hash, MerkleRoot, Receipt, + SignedTransaction, +}; use crate::ProtocolResult; -#[async_trait] -pub trait Executor: Send + Sync { - type Adapter: ExecutorAdapter; - - fn exec(&self) -> ProtocolResult<()>; +#[derive(Clone, Debug)] +pub struct ExecutorExecResp { + pub receipts: Vec, + pub all_cycles_used: Vec, + pub logs_bloom: Bloom, + pub state_root: MerkleRoot, } -#[async_trait] -pub trait ExecutorAdapter: Send + Sync { - fn get_epoch_header(&self) -> ProtocolResult<()>; +pub trait Executor { + fn exec( + &mut self, + epoch_id: u64, + cycles_price: u64, + coinbase: Address, + signed_txs: Vec, + ) -> ProtocolResult; } #[derive(Clone, Debug)] pub struct InvokeContext { - cycle_used: u64, - caller: Address, + pub chain_id: Hash, + pub cycles_used: Fee, + pub cycles_limit: Fee, + pub cycles_price: u64, + pub epoch_id: u64, + pub caller: Address, + pub carrying_asset: Option, + pub coinbase: Address, } +pub type RcInvokeContext = Rc>; + pub trait Dispatcher { fn invoke( &self, - ictx: InvokeContext, + ictx: RcInvokeContext, address: ContractAddress, method: &str, args: Vec, diff --git a/protocol/src/types/mod.rs b/protocol/src/types/mod.rs index 31da97ae9..08c71db06 100644 --- a/protocol/src/types/mod.rs +++ b/protocol/src/types/mod.rs @@ -19,7 +19,7 @@ pub use primitive::{ GENESIS_EPOCH_ID, }; pub use receipt::{Receipt, ReceiptResult}; -pub use transaction::{RawTransaction, SignedTransaction, TransactionAction}; +pub use transaction::{CarryingAsset, RawTransaction, SignedTransaction, TransactionAction}; #[derive(Debug, Display, From)] pub enum TypesError { diff --git a/protocol/src/types/primitive.rs b/protocol/src/types/primitive.rs index daabe7b9b..1a9df94ec 100644 --- a/protocol/src/types/primitive.rs +++ b/protocol/src/types/primitive.rs @@ -128,6 +128,14 @@ impl Address { } } + pub fn from_hex(s: &str) -> ProtocolResult { + let s = clean_0x(s); + let bytes = hex::decode(s).map_err(TypesError::from)?; + + let bytes = Bytes::from(bytes); + Self::from_bytes(bytes) + } + pub fn as_bytes(&self) -> Bytes { match self { Address::User(user) => user.as_bytes(), @@ -250,6 +258,49 @@ impl ContractAddress { }) } + pub fn from_code( + code: Bytes, + nonce: u64, + contract_type: ContractType, + ) -> ProtocolResult { + let nonce_bytes: [u8; 8] = nonce.to_be_bytes(); + let hash_bytes = Hash::digest(Bytes::from( + [code, Bytes::from(nonce_bytes.to_vec())].concat(), + )) + .as_bytes(); + + match contract_type { + ContractType::Asset => Self::from_bytes(Bytes::from( + [ + Bytes::from([ASSET_CONTRACT_ADDRESS_MAGIC].to_vec()), + hash_bytes, + ] + .concat(), + )), + ContractType::App => Self::from_bytes(Bytes::from( + [ + Bytes::from([APP_CONTRACT_ADDRESS_MAGIC].to_vec()), + hash_bytes, + ] + .concat(), + )), + ContractType::Library => Self::from_bytes(Bytes::from( + [ + Bytes::from([LIBRARY_CONTRACT_ADDRESS_MAGIC].to_vec()), + hash_bytes, + ] + .concat(), + )), + ContractType::Native => Self::from_bytes(Bytes::from( + [ + Bytes::from([NATIVE_CONTRACT_ADDRESS_MAGIC].to_vec()), + hash_bytes, + ] + .concat(), + )), + } + } + pub fn from_hex(s: &str) -> ProtocolResult { let s = clean_0x(s); let bytes = hex::decode(s).map_err(TypesError::from)?; @@ -271,7 +322,7 @@ impl ContractAddress { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Asset { pub id: AssetID, pub name: String, diff --git a/protocol/src/types/receipt.rs b/protocol/src/types/receipt.rs index e8388387c..8dbf3e891 100644 --- a/protocol/src/types/receipt.rs +++ b/protocol/src/types/receipt.rs @@ -17,6 +17,7 @@ pub struct Receipt { pub enum ReceiptResult { Transfer { receiver: UserAddress, + asset_id: AssetID, before_amount: Balance, after_amount: Balance, }, diff --git a/protocol/src/types/transaction.rs b/protocol/src/types/transaction.rs index bdbc8f5b1..c3395d142 100644 --- a/protocol/src/types/transaction.rs +++ b/protocol/src/types/transaction.rs @@ -16,9 +16,8 @@ pub struct RawTransaction { #[derive(Clone, Debug, PartialEq, Eq)] pub enum TransactionAction { Transfer { - receiver: UserAddress, - asset_id: AssetID, - amount: Balance, + receiver: UserAddress, + carrying_asset: CarryingAsset, }, Approve { spender: ContractAddress, @@ -30,14 +29,19 @@ pub enum TransactionAction { contract_type: ContractType, }, Call { - contract: ContractAddress, - method: String, - args: Vec, - asset_id: AssetID, - amount: Balance, + contract: ContractAddress, + method: String, + args: Vec, + carrying_asset: Option, }, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CarryingAsset { + pub asset_id: AssetID, + pub amount: Balance, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct SignedTransaction { pub raw: RawTransaction,