Skip to content

Commit

Permalink
Merge pull request #1100 from multiversx/system-sc
Browse files Browse the repository at this point in the history
System SC mock
  • Loading branch information
ovstinga committed Jul 4, 2023
2 parents 4aab4a9 + 27e617c commit 46bf06b
Show file tree
Hide file tree
Showing 16 changed files with 503 additions and 25 deletions.
2 changes: 1 addition & 1 deletion contracts/examples/multisig/interact-rs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
state.toml

# Trace file of interactor tooling
interactor_trace.scen.json
interactor*.scen.json
10 changes: 8 additions & 2 deletions contracts/examples/multisig/tests/multisig_scenario_go_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,14 @@ fn deploy_duplicate_bm_go() {
}

#[test]
fn interactor_trace_go() {
world().run("scenarios/interactor_trace.scen.json");
#[ignore = "system SC not yet implemented"]
fn interactor_nft_go() {
world().run("scenarios/interactor_nft.scen.json");
}

#[test]
fn interactor_wegld_go() {
world().run("scenarios/interactor_wegld.scen.json");
}

#[test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,13 @@ fn deploy_duplicate_bm_rs() {
}

#[test]
fn interactor_trace_rs() {
world().run("scenarios/interactor_trace.scen.json");
fn interactor_nft_rs() {
world().run("scenarios/interactor_nft.scen.json");
}

#[test]
fn interactor_wegld_rs() {
world().run("scenarios/interactor_wegld.scen.json");
}

#[test]
Expand Down
3 changes: 3 additions & 0 deletions framework/scenario/src/scenario/run_vm/set_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ fn execute(state: &mut BlockchainMock, set_state_step: &SetStateStep) {
new_address.new_address.to_vm_address(),
)
}
for new_token_identifier in set_state_step.new_token_identifiers.iter().cloned() {
state.put_new_token_identifier(new_token_identifier)
}
if let Some(block_info_obj) = &*set_state_step.previous_block_info {
update_block_info(&mut state.previous_block_info, block_info_obj);
}
Expand Down
1 change: 1 addition & 0 deletions vm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ rand = "0.8.5"
rand_seeder = "0.2.2"
ed25519-dalek = "1.0.1"
itertools = "0.10.3"
hex-literal = "0.3.1"
bitflags = "1.3.2"

[dependencies.multiversx-chain-vm-executor]
Expand Down
12 changes: 9 additions & 3 deletions vm/src/tx_execution/exec_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use num_bigint::BigUint;
use num_traits::Zero;
use std::{collections::HashMap, rc::Rc};

use super::{execute_builtin_function_or_default, execute_tx_context};
use super::{execute_builtin_function_or_default, execute_tx_context, is_system_sc_address};

pub fn execute_sc_query(tx_input: TxInput, state: BlockchainMock) -> (TxResult, BlockchainMock) {
let state_rc = Rc::new(state);
Expand All @@ -21,7 +21,9 @@ pub fn execute_sc_query(tx_input: TxInput, state: BlockchainMock) -> (TxResult,
}

pub fn execute_sc_call(tx_input: TxInput, mut state: BlockchainMock) -> (TxResult, BlockchainMock) {
state.subtract_tx_gas(&tx_input.from, tx_input.gas_limit, tx_input.gas_price);
if !is_system_sc_address(&tx_input.from) {
state.subtract_tx_gas(&tx_input.from, tx_input.gas_limit, tx_input.gas_price);
}

let state_rc = Rc::new(state);
let tx_cache = TxCache::new(state_rc.clone());
Expand All @@ -35,11 +37,15 @@ pub fn execute_sc_call(tx_input: TxInput, mut state: BlockchainMock) -> (TxResul
(tx_result, state)
}

fn existing_account(state: &BlockchainMock, address: &VMAddress) -> bool {
state.accounts.contains_key(address) || is_system_sc_address(address)
}

pub fn execute_async_call_and_callback(
async_data: AsyncCallTxData,
state: BlockchainMock,
) -> (TxResult, TxResult, BlockchainMock) {
if state.accounts.contains_key(&async_data.to) {
if existing_account(&state, &async_data.to) {
let async_input = async_call_tx_input(&async_data);

let (async_result, state) = sc_call_with_async_and_callback(async_input, state);
Expand Down
32 changes: 21 additions & 11 deletions vm/src/tx_execution/exec_general_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@ use crate::{
types::VMAddress,
};

use super::execute_tx_context;
use super::{execute_system_sc, execute_tx_context, is_system_sc_address};

pub fn default_execution(tx_input: TxInput, tx_cache: TxCache) -> (TxResult, BlockchainUpdate) {
let mut tx_context = TxContext::new(tx_input, tx_cache);

if let Err(err) = tx_context.tx_cache.subtract_egld_balance(
&tx_context.tx_input_box.from,
&tx_context.tx_input_box.egld_value,
) {
return (TxResult::from_panic_obj(&err), BlockchainUpdate::empty());
if !is_system_sc_address(&tx_context.tx_input_box.from) {
if let Err(err) = tx_context.tx_cache.subtract_egld_balance(
&tx_context.tx_input_box.from,
&tx_context.tx_input_box.egld_value,
) {
return (TxResult::from_panic_obj(&err), BlockchainUpdate::empty());
}
}

if !is_system_sc_address(&tx_context.tx_input_box.to) {
tx_context.tx_cache.increase_egld_balance(
&tx_context.tx_input_box.to,
&tx_context.tx_input_box.egld_value,
);
}
tx_context.tx_cache.increase_egld_balance(
&tx_context.tx_input_box.to,
&tx_context.tx_input_box.egld_value,
);

// skip for transactions coming directly from scenario json, which should all be coming from user wallets
// TODO: reorg context logic
Expand Down Expand Up @@ -54,11 +59,16 @@ pub fn default_execution(tx_input: TxInput, tx_cache: TxCache) -> (TxResult, Blo
}
}

let mut tx_result = if !tx_context.tx_input_box.to.is_smart_contract_address()
let recipient_address = &tx_context.tx_input_box.to;
let mut tx_result = if !recipient_address.is_smart_contract_address()
|| tx_context.tx_input_box.func_name.is_empty()
{
// direct EGLD transfer
TxResult::empty()
} else if is_system_sc_address(recipient_address) {
let (tx_context_modified, tx_result) = execute_system_sc(tx_context);
tx_context = tx_context_modified;
tx_result
} else {
let (tx_context_modified, tx_result) = execute_tx_context(tx_context);
tx_context = tx_context_modified;
Expand Down
2 changes: 2 additions & 0 deletions vm/src/tx_execution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ mod exec_call;
mod exec_contract_endpoint;
mod exec_create;
mod exec_general_tx;
mod system_sc;

pub use builtin_function_mocks::*;
pub use exec_call::*;
pub use exec_contract_endpoint::*;
pub use exec_create::*;
pub use exec_general_tx::*;
pub use system_sc::*;
60 changes: 60 additions & 0 deletions vm/src/tx_execution/system_sc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
mod system_sc_issue;
mod system_sc_special_roles;
mod system_sc_unimplemented;

use crate::{
tx_mock::{TxContext, TxResult},
types::VMAddress,
};
use hex_literal::hex;
use system_sc_issue::*;
use system_sc_special_roles::*;
use system_sc_unimplemented::*;

/// Address of the system smart contract that manages ESDT.
/// Bech32: erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u
pub const ESDT_SYSTEM_SC_ADDRESS_ARRAY: [u8; 32] =
hex!("000000000000000000010000000000000000000000000000000000000002ffff");

pub fn is_system_sc_address(address: &VMAddress) -> bool {
address.as_array() == &ESDT_SYSTEM_SC_ADDRESS_ARRAY
}

pub fn execute_system_sc(tx_context: TxContext) -> (TxContext, TxResult) {
let func_name = tx_context.tx_input_box.func_name.clone();
match func_name.as_str() {
"issue" => issue(tx_context),
"issueSemiFungible" => issue_semi_fungible(tx_context),
"issueNonFungible" => issue_non_fungible(tx_context),
"registerMetaESDT" => register_meta_esdt(tx_context),
"changeSFTToMetaESDT" => change_sft_to_meta_esdt(tx_context),
"registerAndSetAllRoles" => register_and_set_all_roles(),
"ESDTBurn" => esdt_burn(tx_context),
"mint" => mint(tx_context),
"freeze" => freeze(tx_context),
"unFreeze" => unfreeze(tx_context),
"wipe" => wipe(tx_context),
"pause" => pause(tx_context),
"unPause" => unpause(tx_context),
"freezeSingleNFT" => freeze_single_nft(tx_context),
"unFreezeSingleNFT" => unfreeze_single_nft(tx_context),
"wipeSingleNFT" => wipe_single_nft(tx_context),
"claim" => claim(tx_context),
"configChange" => config_change(tx_context),
"controlChanges" => control_changes(tx_context),
"transferOwnership" => transfer_ownership(tx_context),
"getTokenProperties" => get_token_properties(tx_context),
"getSpecialRoles" => get_special_roles(tx_context),
"setSpecialRole" => set_special_role(tx_context),
"unSetSpecialRole" => unset_special_role(tx_context),
"transferNFTCreateRole" => transfer_nft_create_role(tx_context),
"stopNFTCreate" => stop_nft_create(tx_context),
"getAllAddressesAndRoles" => get_all_addresses_and_roles(tx_context),
"getContractConfig" => get_contract_config(tx_context),
"changeToMultiShardCreate" => change_to_multi_shard_create(tx_context),
"setBurnRoleGlobally" => set_burn_role_globally(tx_context),
"unsetBurnRoleGlobally" => unset_burn_role_globally(tx_context),
"sendAllTransferRoleAddresses" => send_all_transfer_role_addresses(tx_context),
invalid_func_name => panic!("invalid system SC function: {invalid_func_name}"),
}
}
163 changes: 163 additions & 0 deletions vm/src/tx_execution/system_sc/system_sc_issue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use num_bigint::BigUint;

use crate::{
crypto_functions::keccak256,
tx_mock::{TxCache, TxContext, TxInput, TxResult},
types::top_decode_u64,
};

/// Issues a new token.
pub fn issue(tx_context: TxContext) -> (TxContext, TxResult) {
let tx_input = tx_context.input_ref();
let tx_cache = tx_context.blockchain_cache();
let tx_result: TxResult;

if tx_input.args.len() < 4 {
tx_result = TxResult::from_vm_error("not enough arguments");
return (tx_context, tx_result);
}
let _name = tx_input.args[0].clone();
let ticker = tx_input.args[1].clone();
let _total_supply = BigUint::from_bytes_be(tx_input.args[2].clone().as_ref());
let _decimals = top_decode_u64(tx_input.args[3].clone().as_ref()) as u32;

let mut new_token_identifiers = tx_cache.get_new_token_identifiers();

let token_identifier = if let Some((i, ti)) =
first_token_identifier_with_ticker(&new_token_identifiers, &ticker)
{
new_token_identifiers.remove(i);
ti.into_bytes()
} else {
generate_token_identifier_from_ticker(tx_input, tx_cache, &ticker)
};

println!(
"\n\ngenerated new token_identifier: {}\n\n",
std::str::from_utf8(&token_identifier).unwrap()
);

tx_cache.with_account_mut(&tx_input.from, |account| {
account.esdt.issue_token(&token_identifier);
});
tx_cache.set_new_token_identifiers(new_token_identifiers);

tx_result = TxResult {
result_values: vec![token_identifier],
..Default::default()
};

(tx_context, tx_result)
}

/// Issues a new semi-fungible token.
pub fn issue_semi_fungible(tx_context: TxContext) -> (TxContext, TxResult) {
issue_non_fungible(tx_context)
}

/// Issues a new non-fungible token.
pub fn issue_non_fungible(tx_context: TxContext) -> (TxContext, TxResult) {
let tx_input = tx_context.input_ref();
let tx_cache = tx_context.blockchain_cache();
let tx_result: TxResult;

if tx_input.args.len() < 2 {
tx_result = TxResult::from_vm_error("not enough arguments");
return (tx_context, tx_result);
}
let _name = tx_input.args[0].clone();
let ticker = tx_input.args[1].clone();

let mut new_token_identifiers = tx_cache.get_new_token_identifiers();

let token_identifier = if let Some((i, ti)) =
first_token_identifier_with_ticker(&new_token_identifiers, &ticker)
{
new_token_identifiers.remove(i);
ti.into_bytes()
} else {
generate_token_identifier_from_ticker(tx_input, tx_cache, &ticker)
};

tx_cache.with_account_mut(&tx_input.from, |account| {
account.esdt.issue_token(&token_identifier);
});
tx_cache.set_new_token_identifiers(new_token_identifiers);

tx_result = TxResult {
result_values: vec![token_identifier],
..Default::default()
};

(tx_context, tx_result)
}

fn first_token_identifier_with_ticker(
token_identifiers: &[String],
ticker: &[u8],
) -> Option<(usize, String)> {
let extract_ticker =
|ti: &String| -> String { ti.split('-').map(|x| x.to_string()).next().unwrap() };

token_identifiers
.iter()
.position(|x| extract_ticker(x).as_bytes() == ticker)
.map(|i| (i, token_identifiers[i].clone()))
}

fn generate_token_identifier_from_ticker(
tx_input: &TxInput,
tx_cache: &TxCache,
ticker: &[u8],
) -> Vec<u8> {
let new_random_base = [
tx_input.from.as_bytes(),
tx_cache
.blockchain_ref()
.current_block_info
.block_random_seed
.as_slice(),
]
.concat();
let new_random = keccak256(&new_random_base);
let new_random_for_ticker = &new_random[..3];

let token_identifier = [
ticker,
"-".as_bytes(),
hex::encode(new_random_for_ticker).as_bytes(),
]
.concat();

token_identifier
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_first_token_identifier_with_ticker_ok() {
let ticker = String::from("BBBB").into_bytes();
let new_token_indetifiers = vec![
"AAAA-0123".to_string(),
"BBBB-4567".to_string(),
"BBBB-0123".to_string(),
"CCCC-4567".to_string(),
];

let ti = first_token_identifier_with_ticker(&new_token_indetifiers, &ticker);
let expected = b"BBBB-4567".as_slice();
assert_eq!(expected, ti.unwrap().1.into_bytes());
}

#[test]
fn test_first_token_identifier_with_ticker_is_none() {
let ticker = String::from("BBBB").into_bytes();
let new_token_indetifiers = vec!["AAAA-0123".to_string()];

let i = first_token_identifier_with_ticker(&new_token_indetifiers, &ticker);
let expected = None;
assert_eq!(expected, i);
}
}
Loading

0 comments on commit 46bf06b

Please sign in to comment.