Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System SC mock #1100

Merged
merged 11 commits into from
Jul 4, 2023
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
Loading