fuel-vm 0.35.4

FuelVM interpreter.
Documentation
use crate::{
    interpreter::memory::Memory,
    storage::MemoryStorage,
};

use super::*;
use fuel_tx::{
    field::{
        Inputs,
        Outputs,
    },
    Input,
    Script,
};
use test_case::test_case;

#[test_case(0, 32 => Ok(()); "Can read contract balance")]
fn test_contract_balance(b: Word, c: Word) -> Result<(), RuntimeError> {
    let mut memory: Memory<MEM_SIZE> = vec![1u8; MEM_SIZE].try_into().unwrap();
    memory[b as usize..(b as usize + AssetId::LEN)]
        .copy_from_slice(&[2u8; AssetId::LEN][..]);
    memory[c as usize..(c as usize + ContractId::LEN)]
        .copy_from_slice(&[3u8; ContractId::LEN][..]);
    let contract_id = ContractId::from([3u8; 32]);
    let mut storage = MemoryStorage::new(Default::default(), Default::default());
    storage
        .merkle_contract_asset_id_balance_insert(
            &contract_id,
            &AssetId::from([2u8; 32]),
            33,
        )
        .unwrap();
    let mut pc = 4;

    let mut panic_context = PanicContext::None;
    let input = ContractBalanceCtx {
        storage: &mut storage,
        memory: &mut memory,
        pc: RegMut::new(&mut pc),
        input_contracts: InputContracts::new(
            [&contract_id].into_iter(),
            &mut panic_context,
        ),
    };
    let mut result = 0;

    input.contract_balance(&mut result, b, c)?;

    assert_eq!(pc, 8);
    assert_eq!(result, 33);

    Ok(())
}

#[test_case(true, 0, 50, 32 => Ok(()); "Can transfer from external balance")]
fn test_transfer(external: bool, a: Word, b: Word, c: Word) -> Result<(), RuntimeError> {
    let mut memory: Memory<MEM_SIZE> = vec![1u8; MEM_SIZE].try_into().unwrap();
    memory[a as usize..(a as usize + ContractId::LEN)]
        .copy_from_slice(&[3u8; ContractId::LEN][..]);
    memory[c as usize..(c as usize + AssetId::LEN)]
        .copy_from_slice(&[2u8; AssetId::LEN][..]);
    let contract_id = ContractId::from([3u8; 32]);
    let asset_id = AssetId::from([2u8; 32]);
    let mut storage = MemoryStorage::new(Default::default(), Default::default());
    storage
        .merkle_contract_asset_id_balance_insert(&contract_id, &asset_id, 60)
        .unwrap();
    let mut pc = 4;

    let context = if external {
        Context::Script {
            block_height: Default::default(),
        }
    } else {
        Context::Call {
            block_height: Default::default(),
        }
    };

    let mut balances =
        RuntimeBalances::try_from_iter([(AssetId::from([2u8; 32]), 50)]).unwrap();
    let mut receipts = Default::default();
    let mut panic_context = PanicContext::None;
    let mut tx = Script::default();
    *tx.inputs_mut() = vec![Input::contract(
        Default::default(),
        Default::default(),
        Default::default(),
        Default::default(),
        contract_id,
    )];

    let fp = 0;
    let is = 0;

    let input = TransferCtx {
        storage: &mut storage,
        memory: &mut memory,
        pc: RegMut::new(&mut pc),
        context: &context,
        balances: &mut balances,
        receipts: &mut receipts,
        tx: &mut tx,
        tx_offset: 0,
        fp: Reg::new(&fp),
        is: Reg::new(&is),
    };

    input.transfer(&mut panic_context, a, b, c)?;

    assert_eq!(pc, 8);
    let amount = storage
        .merkle_contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();
    assert_eq!(balances.balance(&asset_id).unwrap(), 0);
    assert_eq!(amount, 60 + b);

    Ok(())
}

#[test_case(true, 0, 0, 50, 32 => Ok(()); "Can transfer from external balance")]
fn test_transfer_output(
    external: bool,
    a: Word,
    b: Word,
    c: Word,
    d: Word,
) -> Result<(), RuntimeError> {
    let mut memory: Memory<MEM_SIZE> = vec![1u8; MEM_SIZE].try_into().unwrap();
    memory[a as usize..(a as usize + Address::LEN)]
        .copy_from_slice(&[3u8; Address::LEN][..]);
    memory[d as usize..(d as usize + AssetId::LEN)]
        .copy_from_slice(&[2u8; AssetId::LEN][..]);
    let contract_id = ContractId::from([3u8; 32]);
    let asset_id = AssetId::from([2u8; 32]);
    let mut storage = MemoryStorage::new(Default::default(), Default::default());
    storage
        .merkle_contract_asset_id_balance_insert(&contract_id, &asset_id, 60)
        .unwrap();
    let mut pc = 4;

    let context = if external {
        Context::Script {
            block_height: Default::default(),
        }
    } else {
        Context::Call {
            block_height: Default::default(),
        }
    };

    let mut balances =
        RuntimeBalances::try_from_iter([(AssetId::from([2u8; 32]), 50)]).unwrap();
    let mut receipts = Default::default();
    let mut tx = Script::default();
    *tx.inputs_mut() = vec![Input::contract(
        Default::default(),
        Default::default(),
        Default::default(),
        Default::default(),
        contract_id,
    )];
    *tx.outputs_mut() = vec![Output::variable(
        Default::default(),
        Default::default(),
        Default::default(),
    )];

    let fp = 0;
    let is = 0;

    let input = TransferCtx {
        storage: &mut storage,
        memory: &mut memory,
        pc: RegMut::new(&mut pc),
        context: &context,
        balances: &mut balances,
        receipts: &mut receipts,
        tx: &mut tx,
        tx_offset: 0,
        fp: Reg::new(&fp),
        is: Reg::new(&is),
    };

    input.transfer_output(a, b, c, d)?;

    assert_eq!(pc, 8);
    let amount = storage
        .merkle_contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();
    assert_eq!(balances.balance(&asset_id).unwrap(), 0);
    assert_eq!(amount, 60 + b);

    Ok(())
}

#[test_case(0, 0 => Ok(()); "Can increase balance by zero")]
#[test_case(None, 0 => Ok(()); "Can initialize balance to zero")]
#[test_case(None, Word::MAX => Ok(()); "Can initialize balance to max")]
#[test_case(0, Word::MAX => Ok(()); "Can add max to zero")]
#[test_case(Word::MAX, 0 => Ok(()); "Can add zero to max")]
#[test_case(1, Word::MAX => Err(RuntimeError::Recoverable(PanicReason::ArithmeticOverflow)); "Overflowing add")]
#[test_case(Word::MAX, 1 => Err(RuntimeError::Recoverable(PanicReason::ArithmeticOverflow)); "Overflowing 1 add")]
fn test_balance_increase(
    initial: impl Into<Option<Word>>,
    amount: Word,
) -> Result<(), RuntimeError> {
    let contract_id = ContractId::from([3u8; 32]);
    let asset_id = AssetId::from([2u8; 32]);
    let mut storage = MemoryStorage::new(Default::default(), Default::default());
    let initial = initial.into();
    if let Some(initial) = initial {
        storage
            .merkle_contract_asset_id_balance_insert(&contract_id, &asset_id, initial)
            .unwrap();
    }

    let result = balance_increase(&mut storage, &contract_id, &asset_id, amount)?;
    let initial = initial.unwrap_or(0);

    assert_eq!(result, initial + amount);

    let result = storage
        .merkle_contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();

    assert_eq!(result, initial + amount);

    Ok(())
}

#[test_case(0, 0 => Ok(()); "Can increase balance by zero")]
#[test_case(None, 0 => Ok(()); "Can initialize balance to zero")]
#[test_case(Word::MAX, 0 => Ok(()); "Can initialize balance to max")]
#[test_case(10, 10 => Ok(()); "Can subtract to zero")]
#[test_case(1, Word::MAX => Err(RuntimeError::Recoverable(PanicReason::NotEnoughBalance)); "Overflowing subtract")]
#[test_case(1, 2 => Err(RuntimeError::Recoverable(PanicReason::NotEnoughBalance)); "Overflowing 1 subtract")]
fn test_balance_decrease(
    initial: impl Into<Option<Word>>,
    amount: Word,
) -> Result<(), RuntimeError> {
    let contract_id = ContractId::from([3u8; 32]);
    let asset_id = AssetId::from([2u8; 32]);
    let mut storage = MemoryStorage::new(Default::default(), Default::default());
    let initial = initial.into();
    if let Some(initial) = initial {
        storage
            .merkle_contract_asset_id_balance_insert(&contract_id, &asset_id, initial)
            .unwrap();
    }

    let result = balance_decrease(&mut storage, &contract_id, &asset_id, amount)?;
    let initial = initial.unwrap_or(0);

    assert_eq!(result, initial - amount);

    let result = storage
        .merkle_contract_asset_id_balance(&contract_id, &asset_id)
        .unwrap()
        .unwrap();

    assert_eq!(result, initial - amount);

    Ok(())
}