fuel-vm 0.66.3

FuelVM interpreter.
Documentation
#![allow(clippy::arithmetic_side_effects, clippy::cast_possible_truncation)]

use alloc::vec;
use core::convert::Infallible;

use crate::{
    consts::*,
    storage::MemoryStorage,
    verification::Normal,
};

use super::*;
use crate::interpreter::PanicContext;
use fuel_storage::StorageAsMut;
use test_case::test_case;

#[test_case(false, 0, None, 0, [0; 32] => Ok(()); "Burn nothing")]
#[test_case(false, 0, 0, 0, [0; 32] => Ok(()); "Burn is idempotent")]
#[test_case(false, 0, 100, 100, [0; 32] => Ok(()); "Burn all")]
#[test_case(false, 0, 100, 100, [15; 32] => Ok(()); "Burn all for another sub id")]
#[test_case(false, 0, 100, 10, [0; 32] => Ok(()); "Burn some")]
#[test_case(true, 0, 100, 10, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::ExpectedInternalContext)); "Can't burn from external context")]
#[test_case(false, 1, 100, 10, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::NotEnoughBalance)); "Can't burn when contract id not in memory")]
#[test_case(false, 0, 100, 101, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::NotEnoughBalance)); "Can't burn too much")]
#[test_case(false, 0, None, 1, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::NotEnoughBalance)); "Can't burn when no balance")]
fn test_burn(
    external: bool,
    fp: Word,
    initialize: impl Into<Option<Word>>,
    amount: Word,
    sub_id: [u8; 32],
) -> IoResult<(), Infallible> {
    let mut storage = MemoryStorage::default();
    let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap();
    let contract_id = ContractId::from([3u8; 32]);
    memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice());
    memory[ContractId::LEN..ContractId::LEN + Bytes32::LEN]
        .copy_from_slice(sub_id.as_slice());
    let sub_id = SubAssetId::from(sub_id);
    let asset_id = contract_id.asset_id(&sub_id);
    let initialize = initialize.into();
    if let Some(initialize) = initialize {
        let old_balance = storage
            .contract_asset_id_balance_replace(&contract_id, &asset_id, initialize)
            .unwrap();
        assert!(old_balance.is_none());
    }
    let context = if external {
        Context::Script {
            block_height: Default::default(),
        }
    } else {
        Context::Call {
            block_height: Default::default(),
        }
    };
    let mut receipts = Default::default();

    let is = 0;
    const ORIGINAL_PC: Word = 4;
    let mut pc = ORIGINAL_PC;
    BurnCtx {
        storage: &mut storage,
        context: &context,
        receipts: &mut receipts,
        memory: &mut memory,
        fp: Reg::new(&fp),
        pc: RegMut::new(&mut pc),
        is: Reg::new(&is),
    }
    .burn(amount, ContractId::LEN as Word)?;
    assert_eq!(pc, 8);
    let result = storage
        .contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();
    assert_eq!(result, initialize.unwrap_or(0) - amount);
    assert_eq!(receipts.len(), 1);
    assert_eq!(
        receipts[0],
        Receipt::Burn {
            sub_id,
            contract_id,
            val: amount,
            pc: ORIGINAL_PC,
            is
        }
    );
    Ok(())
}

#[test_case(false, 0, None, 0, [0; 32] => Ok(()); "Mint nothing")]
#[test_case(false, 0, 0, 0, [0; 32] => Ok(()); "Mint is idempotent")]
#[test_case(false, 0, 100, 0, [0; 32] => Ok(()); "Mint is idempotent any")]
#[test_case(false, 0, 100, 100, [0; 32] => Ok(()); "Mint Double")]
#[test_case(false, 0, 100, 100, [15; 32] => Ok(()); "Mint Double for another sub id")]
#[test_case(false, 0, 100, 10, [0; 32] => Ok(()); "Mint some")]
#[test_case(false, 0, None, 10, [0; 32] => Ok(()); "Mint some from nothing")]
#[test_case(false, 0, 0, 10, [0; 32] => Ok(()); "Mint some from zero")]
#[test_case(false, 0, None, Word::MAX, [0; 32] => Ok(()); "Mint max from nothing")]
#[test_case(false, 0, 0, Word::MAX, [0; 32] => Ok(()); "Mint max from zero")]
#[test_case(true, 0, 100, 10, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::ExpectedInternalContext)); "Can't mint from external context")]
#[test_case(false, 0, 1, Word::MAX, [0; 32] => Err(RuntimeError::Recoverable(PanicReason::BalanceOverflow)); "Can't mint too much")]
fn test_mint(
    external: bool,
    fp: Word,
    initialize: impl Into<Option<Word>>,
    amount: Word,
    sub_id: [u8; 32],
) -> IoResult<(), Infallible> {
    let mut storage = MemoryStorage::default();
    let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap();
    let contract_id = ContractId::from([3u8; 32]);
    memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice());
    memory[ContractId::LEN..ContractId::LEN + Bytes32::LEN]
        .copy_from_slice(sub_id.as_slice());
    let sub_id = SubAssetId::from(sub_id);
    let asset_id = contract_id.asset_id(&sub_id);
    let initialize = initialize.into();
    if let Some(initialize) = initialize {
        let old_balance = storage
            .contract_asset_id_balance_replace(&contract_id, &asset_id, initialize)
            .unwrap();
        assert!(old_balance.is_none());
    }
    let context = if external {
        Context::Script {
            block_height: Default::default(),
        }
    } else {
        Context::Call {
            block_height: Default::default(),
        }
    };

    let mut receipts = Default::default();

    let is = 0;
    const ORIGINAL_PC: Word = 4;
    let mut pc = ORIGINAL_PC;
    let mut cgas = 10_000;
    let mut ggas = 10_000;
    MintCtx {
        storage: &mut storage,
        context: &context,
        receipts: &mut receipts,
        memory: &mut memory,

        new_storage_gas_per_byte: 1,
        cgas: RegMut::new(&mut cgas),
        ggas: RegMut::new(&mut ggas),
        fp: Reg::new(&fp),
        pc: RegMut::new(&mut pc),
        is: Reg::new(&is),
    }
    .mint(amount, ContractId::LEN as Word)?;
    assert_eq!(pc, 8);
    let result = storage
        .contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();
    assert_eq!(result, initialize.unwrap_or(0) + amount);
    assert_eq!(receipts.len(), 1);
    assert_eq!(
        receipts[0],
        Receipt::Mint {
            sub_id,
            contract_id,
            val: amount,
            pc: ORIGINAL_PC,
            is
        }
    );
    Ok(())
}

#[test]
fn test_block_hash() {
    let storage = MemoryStorage::default();
    let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap();
    let owner = OwnershipRegisters {
        sp: 1000,
        ssp: 1,
        hp: 2000,
        prev_hp: 3000,
    };
    let mut pc = 4;
    block_hash(&storage, &mut memory, owner, RegMut::new(&mut pc), 20, 40).unwrap();
    assert_eq!(pc, 8);
    assert_ne!(memory[20..20 + 32], [1u8; 32]);
}

#[test]
fn test_block_height() {
    let context = Context::Script {
        block_height: 20.into(),
    };
    let mut pc = 4;
    let mut result = 0;
    block_height(&context, RegMut::new(&mut pc), &mut result).unwrap();
    assert_eq!(pc, 8);
    assert_eq!(result, 20);
}

#[test]
fn test_coinbase() {
    let storage = MemoryStorage::new(Default::default(), ContractId::zeroed());
    let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap();
    let owner = OwnershipRegisters {
        sp: 1000,
        ssp: 1,
        hp: 2000,
        prev_hp: 3000,
    };
    let mut pc = 4;
    coinbase(&storage, &mut memory, owner, RegMut::new(&mut pc), 20).unwrap();
    assert_eq!(pc, 8);
    assert_eq!(memory[20..20 + 32], [0u8; 32]);
}

#[test]
fn test_code_size() {
    let contract_id = ContractId::new([3u8; ContractId::LEN]);
    let mut storage = MemoryStorage::default();
    let mut memory: MemoryInstance = vec![1u8; MEM_SIZE].try_into().unwrap();
    memory[0..ContractId::LEN].copy_from_slice(contract_id.as_slice());
    StorageAsMut::storage::<ContractsRawCode>(&mut storage)
        .write_bytes(&ContractId::from([3u8; 32]), &[1u8; 100])
        .unwrap();
    let mut pc = 4;
    let mut cgas = 0;
    let mut ggas = 0;
    let input_contracts = [contract_id];
    let input_contracts = input_contracts.into_iter().collect();
    let mut panic_context = PanicContext::None;
    let input = CodeSizeCtx {
        storage: &mut storage,
        memory: &mut memory,
        gas_cost: DependentCost::free(),
        input_contracts: &input_contracts,
        panic_context: &mut panic_context,
        cgas: RegMut::new(&mut cgas),
        ggas: RegMut::new(&mut ggas),
        pc: RegMut::new(&mut pc),
        verifier: &mut Normal,
    };
    let mut result = 0;
    let _ = input
        .code_size(&mut result, 1)
        .expect_err("Contract is not found");
    assert_eq!(pc, 4);

    let input = CodeSizeCtx {
        storage: &mut storage,
        memory: &mut memory,
        gas_cost: DependentCost::free(),
        input_contracts: &input_contracts,
        panic_context: &mut panic_context,
        cgas: RegMut::new(&mut cgas),
        ggas: RegMut::new(&mut ggas),
        pc: RegMut::new(&mut pc),
        verifier: &mut Normal,
    };
    let mut result = 0;
    input.code_size(&mut result, 0).unwrap();
    assert_eq!(pc, 8);
    assert_eq!(result, 100);

    let input_contracts = Default::default();
    let input = CodeSizeCtx {
        storage: &mut storage,
        memory: &mut memory,
        gas_cost: DependentCost::free(),
        input_contracts: &input_contracts,
        panic_context: &mut panic_context,
        cgas: RegMut::new(&mut cgas),
        ggas: RegMut::new(&mut ggas),
        pc: RegMut::new(&mut pc),
        verifier: &mut Normal,
    };
    let mut result = 0;
    let _ = input
        .code_size(&mut result, 0)
        .expect_err("The contract is not in the input");
}

#[test]
fn test_timestamp() {
    let storage = MemoryStorage::default();
    let mut pc = 4;
    let mut result = 0;
    let _ = timestamp(
        &storage,
        Default::default(),
        RegMut::new(&mut pc),
        &mut result,
        1,
    )
    .expect_err("Height is greater then current block height");
    let _ = timestamp(
        &storage,
        u32::MAX.into(),
        RegMut::new(&mut pc),
        &mut result,
        u32::MAX as Word + 1,
    )
    .expect_err("Height doesn't fit into a u32");
    assert_eq!(pc, 4);

    timestamp(
        &storage,
        Default::default(),
        RegMut::new(&mut pc),
        &mut result,
        0,
    )
    .unwrap();
    assert_eq!(pc, 8);

    timestamp(&storage, 20.into(), RegMut::new(&mut pc), &mut result, 19).unwrap();
    assert_eq!(pc, 12);
}