GithubHelp home page GithubHelp logo

fuel-vm's Introduction

Fuel execution environment

builddiscord

The repository contains crates implementing the FuelVM specification used by fuel-core and the Sway compiler.

Crates living here

Crate Version Description
fuel-asm crates.io Contains the FuelVM instruction set - opcodes used by the Sway and VM.
fuel-crypto crates.io Cryptographic primitives used across Fuel Rust based projects.
fuel-merkle crates.io Implementations of the Merkle Tree used by the fuel-core to fulfill fraud proofs requirements, and fuel-tx to validate transaction validity.
fuel-storage crates.io Storage abstraction is used to connect FuelVM, fuel-merkle, and fuel-core together without direct access.
fuel-tx crates.io Contains a definition of types from the specification, with canonical serialization and deserialization. The Transaction and Checked<Tx> type implements fee calculation and validation of rules defined by the specification.
fuel-types crates.io Atomic types are used by almost all Fuel Rust-based crates. The crate defines the most common entities and implements their serialization/deserialization.
fuel-vm crates.io The VM itself executes fuel-asm opcodes generated by the Sway compiler. It is used as a core component of the fuel-core block executor to validate, estimate, and execute Create and Script transactions.

Testing

The ci_checks.sh script file can be used to run all CI checks, including the running of tests.

source ci_checks.sh

The script requires pre-installed tools. For more information run:

cat ci_checks.sh

Bug reporting and reproduction

If you find any bug or unexpected behavior, please open an issue. It would be helpful to provide a scenario of how to reproduce the problem:

  • A text description of the problem(maybe with links)
  • A runnable script with instruction
  • A repository with reproduction code and instructions on how to compile/run
  • A unit test with the usage of the pure opcodes from fuel-asm

How to use pure opcodes

The fuel-vm has many unit tests, almost for each opcode. Supporting a huge test codebase requires proper test utils that you can re-use to reproduce a bug.

Add a new test case

If a specific opcode has unexpected behaviors, maybe there is a unit test already that you can reuse to reproduce a bug. You need to add a new test_case like:

#[test_case(JumpMode::Absolute, 0, 0, 100 => Ok(4); "absolute jump")]

Before the test and run this specific test or all tests.

Build custom scripts

If you need to write your own specific script and run it, you can use test_helpers::run_script.

For example:

#[test]
fn dynamic_call_frame_ops_bug_missing_ssp_check() {
    let ops = vec![
        op::cfs(RegId::SP),
        op::slli(0x10, RegId::ONE, 26),
        op::aloc(0x10),
        op::sw(RegId::ZERO, 0x10, 0),
        op::ret(RegId::ONE),
    ];
    let receipts = run_script(ops);
    assert_panics(&receipts, PanicReason::MemoryOverflow);
}

It returns receipts that contain result of execution. The assert_panics can be used to check for panics.

Build custom transactions

The fuel-tx provides fuel_tx::TransactionBuilder that simplifies the building of custom transaction for testing purposes.

You can check how TransactionBuilder::script or TransactionBuilder::create are used for better understanding.

fuel-vm's People

Contributors

adlerjohn avatar alicanc avatar arboleya avatar br1ght0ne avatar braqzen avatar bvrooman avatar controlcpluscontrolv avatar deekerno avatar dentosal avatar digorithm avatar ellioty avatar eureka-cpu avatar freesig avatar iqdecay avatar kayagokalp avatar leviathanbeak avatar mediremi avatar mitchmindtree avatar mitchturner avatar mohammadfawaz avatar otrho avatar rakita avatar salka1988 avatar sdankel avatar segfault-magnet avatar silentcicero avatar swaystar123 avatar vlopes11 avatar voxelot avatar xgreenx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fuel-vm's Issues

set receipt root on serialized tx in memory

In order to keep the serialized representation of the tx consistent with it's malleated state, set the receipt_root on the serialized form of the transaction as well. Keeping the serialized representation of the tx will allow it to be easily copied and reused for merkle hashing later on.

#93 (comment)

Arithmetic ops don't set `$of` to overflow value, but overflow state

This line

self.alu_overflow(ra, Word::overflowing_mul, b, c)?;
sets the $of register to the second return value of overflowing_mul, which is a Boolean indicating whether an overflow occurred or not.

The specs actually say that the overflow value should be placed into $of in the case of overflow, no simple a Boolean.

At the very least this needs to be done for add, subtract, multiply, divide, and mod. Pow can be left as-is.

Transaction Output Mutation

According to the specs, the VM needs to modify the transaction in-memory to finalize output amounts for Change and Variable outputs.

After execution, the following is extracted:

  1. The transaction in-memory on VM termination is used as the final transaction which is included in the block.
  • Variable outputs need to be updated on the fly as VM execution progresses
  • The free balances are only moved into change outputs at the very end of execution, once.

ScriptResult inconsistent with RVRT opcode and ProgramState

When a contract or script uses the revert opcode, the interpreter returns the program state as Revert(_). However, the script result receipt returns as successful (0 / RESERV00).

The expected behavior would be that the script result would return the RVRT in the instruction result and use PanicReason::Revert.

The docs for panic reason say:

/// Panic reason representation for the interpreter.
pub enum PanicReason {
    /// Found `RVRT` instruction.
    Revert = 0x01,

However, when I run a script that reverts, the panic reason is returned as RESERV00.

repro:

#[test]
fn revert_returns_value() {
    let script = vec![Opcode::RVRT(REG_ONE)];
    let result = TestBuilder::new(2322).script(script).execute();
    eprintln!("{:#?}", result);
}

Improve error handling and feedback

Currently the error structure is centralized into InterpreterError::Opcode(op) to facilitate the development process and debug.

However, the error structure should be improved to contain the ID of the current contract, runtime information such as $pc, the current opcode, the involved registers and some stack trace data

Shield `unsafe` behind `optimized` feature

Implement a new feature optimized to shield all unsafe usage.

When this feature is on, unsafe is allowed

When this feature is off, we should introduce forbid_unsafe in the root of the library:
src/lib.rs: #![cfg_attr(not(feature = "optimized"), forbid(unsafe_code))]

This means that all functions that use unsafe will have to be reimplemented with their safer (and potentially slower) counterparts

Implement the `SMO` opcode

This is needed to implement the L1 <=> L2 Bridge, specifically for the L2ERC20Gateway contract, but most likely in more places as well.
cc @vlopes11

Incorrect opcodes being executed after contract call

This was originally surfaced by FuelLabs/sway#135 when testing contract calls.

The two transactions below describe a contract deployment and then calling that contract (to the address provided by the seed in both the VM testing and Sway's test suite). While the contract is successfully located, it is found to have a program of all null bytes, resulting in Undefined opcode errors.

Contract deploy bytes:

[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 39, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 95, 241, 169, 232, 11, 103, 33, 18, 196, 97, 138, 125, 107, 66, 162, 19, 18, 18, 63, 44, 175, 173, 97, 21, 67, 224, 38, 93, 137, 241, 194, 182, 0, 0, 0, 0, 0, 0, 0, 5, 161, 61, 115, 13, 198, 248, 9, 193, 241, 242, 212, 52, 230, 24, 126, 237, 15, 87, 50, 233, 32, 190, 40, 39, 252, 222, 86, 15, 152, 198, 140, 204, 0, 0, 0, 0, 0, 0, 0, 12, 92, 0, 0, 0, 240, 0, 0, 0, 52, 64, 0, 0, 0, 0, 0, 0]

Contract call bytes:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 39, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 4, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 92, 67, 184, 192, 1, 16, 186, 227, 0, 67, 66, 224, 13, 16, 65, 3, 0, 67, 70, 224, 4, 67, 74, 224, 5, 67, 78, 224, 6, 67, 82, 224, 7, 31, 85, 0, 0, 67, 66, 224, 14, 16, 65, 3, 0, 31, 88, 80, 0, 64, 0, 0, 48, 67, 94, 224, 12, 71, 89, 5, 192, 74, 89, 16, 4, 74, 89, 32, 5, 83, 89, 69, 83, 52, 64, 0, 0, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 0, 0, 0, 0, 253, 49, 221, 178, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 161, 61, 115, 13, 198, 248, 9, 193, 241, 242, 212, 52, 230, 24, 126, 237, 15, 87, 50, 233, 32, 190, 40, 39, 252, 222, 86, 15, 152, 198, 140, 204, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 92, 0, 0, 0, 0, 0, 0, 0, 156, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161, 61, 115, 13, 198, 248, 9, 193, 241, 242, 212, 52, 230, 24, 126, 237, 15, 87, 50, 233, 32, 190, 40, 39, 252, 222, 86, 15, 152, 198, 140, 204, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Extract `WORD_SIZE` const to a single place

Right now, WORD_SIZE is being redefined in many places [0] and these definitions aren't pub, forcing external dependencies to redefine the same const. The origin of this discussion happened here FuelLabs/fuels-rs#9 (comment).

At some point, we should put this const definition in a single place and make it pub.

[0]:

  1. https://github.com/FuelLabs/fuel-vm/blob/master/src/interpreter/blockchain.rs#L11
  2. https://github.com/FuelLabs/fuel-vm/blob/master/src/call.rs#L10
  3. https://github.com/FuelLabs/fuel-vm/blob/master/src/interpreter/initialization.rs#L15
  4. https://github.com/FuelLabs/fuel-vm/blob/master/src/interpreter/internal.rs#L13
  5. https://github.com/FuelLabs/fuel-vm/blob/master/src/interpreter/executors/instruction.rs#L14

Breakpoints

This will be more useful when source mapping is available in Sway.

Call receipt has wrong `id`

In the Call receipt when running a script, the id seems to be the same as to. However, according to the specs, id/from should be the callee, in the case of a script 0x0000....

Refactor `execute` function

Currently we have several match patterns for every opcode that is executed.

We should perform only one match, and that is for the opcode identifier. The remainder opcode data must be parsed by default and used when applicable.

LDC: Load code from an external contract

Implement LDC opcode

Steps

  1. Implement a new function load_contract_code in src/interpreter/blockchain.rs
  2. This function should return an error if $ssp + $rC > $hp || $rA + Bytes32::LEN > VM_MAX_RAM || $rC > cmp::min(CONTRACT_MAX_SIZE, MEM_MAX_ACCESS_SIZE) || $ssp != $sp
    1. $ra is obtained via self.registers[ra]. ra is guaranteed to fit self.registers due to the opcode structure
    2. $ssp, $sp and $hp are obtained via self.registers[REG_SSP | REG_SP | REG_HP]
  3. Fetch the contract ID from $rA
    1. To fetch the contract ID from memory, a similar code to blockchain.rs should be used - but using rA instead
  4. Check if id is in tx.inputs
    1. An iterator over the contract IDs in the transaction inputs can be obtained via self.tx.input_contracts()
  5. Fetch the contract code from the storage
    1. To fetch the contract code, Interpreter::contract should be used - as in frame.rs
  6. Calculate the word aligned padded len based on $rC
    1. Use fuel_types::bytes::padded_len
  7. Fetch the code contract.as_ref()[$rb..$rb + len]
    1. Should be zeroes if the padded len is bigger than the contract length code.as_ref().len()
  8. Increment the frame code size by len defined in memory
    1. Memory: self.memory[$fp + ContractId::LEN + Color::LEN + WORD_SIZE * VM_REGISTER_COUNT, WORD_SIZE]
  9. Push the contract code to the stack
    1. Check Interpreter::push_stack
  10. Increment the stack pointer $sp += len
  11. Optionally, append the copied code to self.frame.last.code
  12. Implement the call to this function in the opcodes execution

Benchmarking

Using criterion or similar frameworks, implement some basic benchmarks for fuel-vm.

Some benchmark ideas:

  • script tx with only predicates
  • script tx calling a contract
  • script tx with nested contract calls
  • create tx with a simple contract
  • create tx with storage slots + a contract

Refactor instruction executors

The current instruction executors are duplicating code because the implementation is split into one storage backend that supports contract instructions and the other that won't

pub(crate) fn instruction_predicate(&mut self, instruction: Instruction) -> Result<ExecuteState, RuntimeError> {
let (op, ra, rb, rc, rd, imm) = instruction.into_inner();
let (a, b, c, d) = (
self.registers[ra],
self.registers[rb],
self.registers[rc],
self.registers[rd],
);
match op {
OpcodeRepr::ADD => {
self.gas_charge(GAS_ADD)?;
self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), c.into())?;
}
OpcodeRepr::ADDI => {
self.gas_charge(GAS_ADDI)?;
self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), imm.into())?;
}
OpcodeRepr::AND => {
self.gas_charge(GAS_AND)?;
self.alu_set(ra, b & c)?;
}
OpcodeRepr::ANDI => {
self.gas_charge(GAS_ANDI)?;
self.alu_set(ra, b & imm)?;
}
OpcodeRepr::DIV => {
self.gas_charge(GAS_DIV)?;
self.alu_error(ra, Word::div, b, c, c == 0)?;
}
OpcodeRepr::DIVI => {
self.gas_charge(GAS_DIVI)?;
self.alu_error(ra, Word::div, b, imm, imm == 0)?;
}
OpcodeRepr::EQ => {
self.gas_charge(GAS_EQ)?;
self.alu_set(ra, (b == c) as Word)?;
}
OpcodeRepr::EXP => {
self.gas_charge(GAS_EXP)?;
self.alu_boolean_overflow(ra, Word::overflowing_pow, b, c as u32)?;
}
OpcodeRepr::EXPI => {
self.gas_charge(GAS_EXPI)?;
self.alu_boolean_overflow(ra, Word::overflowing_pow, b, imm as u32)?;
}
OpcodeRepr::GT => {
self.gas_charge(GAS_GT)?;
self.alu_set(ra, (b > c) as Word)?;
}
OpcodeRepr::LT => {
self.gas_charge(GAS_LT)?;
self.alu_set(ra, (b < c) as Word)?;
}
OpcodeRepr::MLOG => {
self.gas_charge(GAS_MLOG)?;
self.alu_error(
ra,
|b, c| (b as f64).log(c as f64).trunc() as Word,
b,
c,
b == 0 || c <= 1,
)?;
}
OpcodeRepr::MOD => {
self.gas_charge(GAS_MOD)?;
self.alu_error(ra, Word::wrapping_rem, b, c, c == 0)?;
}
OpcodeRepr::MODI => {
self.gas_charge(GAS_MODI)?;
self.alu_error(ra, Word::wrapping_rem, b, imm, imm == 0)?;
}
OpcodeRepr::MOVE => {
self.gas_charge(GAS_MOVE)?;
self.alu_set(ra, b)?;
}
OpcodeRepr::MOVI => {
self.gas_charge(GAS_MOVI)?;
self.alu_set(ra, imm)?;
}
OpcodeRepr::MROO => {
self.gas_charge(GAS_MROO)?;
self.alu_error(
ra,
|b, c| (b as f64).powf((c as f64).recip()).trunc() as Word,
b,
c,
c == 0,
)?;
}
OpcodeRepr::MUL => {
self.gas_charge(GAS_MUL)?;
self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), c.into())?;
}
OpcodeRepr::MULI => {
self.gas_charge(GAS_MULI)?;
self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), imm.into())?;
}
OpcodeRepr::NOOP => {
self.gas_charge(GAS_NOOP)?;
self.alu_clear()?;
}
OpcodeRepr::NOT => {
self.gas_charge(GAS_NOT)?;
self.alu_set(ra, !b)?;
}
OpcodeRepr::OR => {
self.gas_charge(GAS_OR)?;
self.alu_set(ra, b | c)?;
}
OpcodeRepr::ORI => {
self.gas_charge(GAS_ORI)?;
self.alu_set(ra, b | imm)?;
}
OpcodeRepr::SLL => {
self.gas_charge(GAS_SLL)?;
self.alu_set(ra, b.checked_shl(c as u32).unwrap_or_default())?;
}
OpcodeRepr::SLLI => {
self.gas_charge(GAS_SLLI)?;
self.alu_set(ra, b.checked_shl(imm as u32).unwrap_or_default())?;
}
OpcodeRepr::SRL => {
self.gas_charge(GAS_SRL)?;
self.alu_set(ra, b.checked_shr(c as u32).unwrap_or_default())?;
}
OpcodeRepr::SRLI => {
self.gas_charge(GAS_SRLI)?;
self.alu_set(ra, b.checked_shr(imm as u32).unwrap_or_default())?;
}
OpcodeRepr::SUB => {
self.gas_charge(GAS_SUB)?;
self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), c.into())?;
}
OpcodeRepr::SUBI => {
self.gas_charge(GAS_SUBI)?;
self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), imm.into())?;
}
OpcodeRepr::XOR => {
self.gas_charge(GAS_XOR)?;
self.alu_set(ra, b ^ c)?;
}
OpcodeRepr::XORI => {
self.gas_charge(GAS_XORI)?;
self.alu_set(ra, b ^ imm)?;
}
OpcodeRepr::CIMV => {
self.gas_charge(GAS_CIMV)?;
self.check_input_maturity(ra, b, c)?;
}
OpcodeRepr::CTMV => {
self.gas_charge(GAS_CTMV)?;
self.check_tx_maturity(ra, b)?;
}
OpcodeRepr::JI => {
self.gas_charge(GAS_JI)?;
self.jump(imm)?;
}
OpcodeRepr::JNEI => {
self.gas_charge(GAS_JNEI)?;
self.jump_not_equal_imm(a, b, imm)?;
}
OpcodeRepr::JNZI => {
self.gas_charge(GAS_JNZI)?;
self.jump_not_zero_imm(a, imm)?;
}
OpcodeRepr::RET => {
self.gas_charge(GAS_RET)?;
self.ret(a)?;
return Ok(ExecuteState::Return(a));
}
OpcodeRepr::ALOC => {
self.gas_charge(GAS_ALOC)?;
self.malloc(a)?;
}
OpcodeRepr::CFEI => {
self.gas_charge(GAS_CFEI)?;
self.stack_pointer_overflow(Word::overflowing_add, imm)?;
}
OpcodeRepr::CFSI => {
self.gas_charge(GAS_CFSI)?;
self.stack_pointer_overflow(Word::overflowing_sub, imm)?;
}
OpcodeRepr::LB => {
self.gas_charge(GAS_LB)?;
self.load_byte(ra, rb, imm)?;
}
OpcodeRepr::LW => {
self.gas_charge(GAS_LW)?;
self.load_word(ra, b, imm)?;
}
OpcodeRepr::MCL => {
self.gas_charge_monad(GAS_MCL, b)?;
self.memclear(a, b)?;
}
OpcodeRepr::MCLI => {
self.gas_charge_monad(GAS_MCLI, b)?;
self.memclear(a, imm)?;
}
OpcodeRepr::MCP => {
self.gas_charge_monad(GAS_MCP, c)?;
self.memcopy(a, b, c)?;
}
OpcodeRepr::MCPI => {
self.gas_charge_monad(GAS_MCPI, imm)?;
self.memcopy(a, b, imm)?;
}
OpcodeRepr::MEQ => {
self.gas_charge(GAS_MEQ)?;
self.memeq(ra, b, c, d)?;
}
OpcodeRepr::SB => {
self.gas_charge(GAS_SB)?;
self.store_byte(a, b, imm)?;
}
OpcodeRepr::SW => {
self.gas_charge(GAS_SW)?;
self.store_word(a, b, imm)?;
}
OpcodeRepr::ECR => {
self.gas_charge(GAS_ECR)?;
self.ecrecover(a, b, c)?;
}
OpcodeRepr::K256 => {
self.gas_charge(GAS_K256)?;
self.keccak256(a, b, c)?;
}
OpcodeRepr::S256 => {
self.gas_charge(GAS_S256)?;
self.sha256(a, b, c)?;
}
OpcodeRepr::XIL => {
self.gas_charge(GAS_XIL)?;
self.transaction_input_length(ra, b)?;
}
OpcodeRepr::XIS => {
self.gas_charge(GAS_XIS)?;
self.transaction_input_start(ra, b)?;
}
OpcodeRepr::XOL => {
self.gas_charge(GAS_XOL)?;
self.transaction_output_length(ra, b)?;
}
OpcodeRepr::XOS => {
self.gas_charge(GAS_XOS)?;
self.transaction_output_start(ra, b)?;
}
OpcodeRepr::XWL => {
self.gas_charge(GAS_XWL)?;
self.transaction_witness_length(ra, b)?;
}
OpcodeRepr::XWS => {
self.gas_charge(GAS_XWS)?;
self.transaction_witness_start(ra, b)?;
}
OpcodeRepr::FLAG => {
self.gas_charge(GAS_FLAG)?;
self.set_flag(a)?;
}
OpcodeRepr::GM => {
self.gas_charge(GAS_GM)?;
self.metadata(ra, imm as Immediate18)?;
}
_ => {
// TODO use dedicated panic reason variant
// https://github.com/FuelLabs/fuel-asm/issues/69
return Err(PanicReason::TransactionValidity.into());
}
}
Ok(ExecuteState::Proceed)
}
}
impl<S> Interpreter<S>
where
S: InterpreterStorage,
{
pub(crate) fn instruction_script(&mut self, instruction: Instruction) -> Result<ExecuteState, RuntimeError> {
let (op, ra, rb, rc, rd, imm) = instruction.into_inner();
let (a, b, c, d) = (
self.registers[ra],
self.registers[rb],
self.registers[rc],
self.registers[rd],
);
match op {
OpcodeRepr::ADD => {
self.gas_charge(GAS_ADD)?;
self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), c.into())?;
}
OpcodeRepr::ADDI => {
self.gas_charge(GAS_ADDI)?;
self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), imm.into())?;
}
OpcodeRepr::AND => {
self.gas_charge(GAS_AND)?;
self.alu_set(ra, b & c)?;
}
OpcodeRepr::ANDI => {
self.gas_charge(GAS_ANDI)?;
self.alu_set(ra, b & imm)?;
}
OpcodeRepr::DIV => {
self.gas_charge(GAS_DIV)?;
self.alu_error(ra, Word::div, b, c, c == 0)?;
}
OpcodeRepr::DIVI => {
self.gas_charge(GAS_DIVI)?;
self.alu_error(ra, Word::div, b, imm, imm == 0)?;
}
OpcodeRepr::EQ => {
self.gas_charge(GAS_EQ)?;
self.alu_set(ra, (b == c) as Word)?;
}
OpcodeRepr::EXP => {
self.gas_charge(GAS_EXP)?;
self.alu_boolean_overflow(ra, Word::overflowing_pow, b, c as u32)?;
}
OpcodeRepr::EXPI => {
self.gas_charge(GAS_EXPI)?;
self.alu_boolean_overflow(ra, Word::overflowing_pow, b, imm as u32)?;
}
OpcodeRepr::GT => {
self.gas_charge(GAS_GT)?;
self.alu_set(ra, (b > c) as Word)?;
}
OpcodeRepr::LT => {
self.gas_charge(GAS_LT)?;
self.alu_set(ra, (b < c) as Word)?;
}
OpcodeRepr::MLOG => {
self.gas_charge(GAS_MLOG)?;
self.alu_error(
ra,
|b, c| (b as f64).log(c as f64).trunc() as Word,
b,
c,
b == 0 || c <= 1,
)?;
}
OpcodeRepr::MOD => {
self.gas_charge(GAS_MOD)?;
self.alu_error(ra, Word::wrapping_rem, b, c, c == 0)?;
}
OpcodeRepr::MODI => {
self.gas_charge(GAS_MODI)?;
self.alu_error(ra, Word::wrapping_rem, b, imm, imm == 0)?;
}
OpcodeRepr::MOVE => {
self.gas_charge(GAS_MOVE)?;
self.alu_set(ra, b)?;
}
OpcodeRepr::MOVI => {
self.gas_charge(GAS_MOVI)?;
self.alu_set(ra, imm)?;
}
OpcodeRepr::MROO => {
self.gas_charge(GAS_MROO)?;
self.alu_error(
ra,
|b, c| (b as f64).powf((c as f64).recip()).trunc() as Word,
b,
c,
c == 0,
)?;
}
OpcodeRepr::MUL => {
self.gas_charge(GAS_MUL)?;
self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), c.into())?;
}
OpcodeRepr::MULI => {
self.gas_charge(GAS_MULI)?;
self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), imm.into())?;
}
OpcodeRepr::NOOP => {
self.gas_charge(GAS_NOOP)?;
self.alu_clear()?;
}
OpcodeRepr::NOT => {
self.gas_charge(GAS_NOT)?;
self.alu_set(ra, !b)?;
}
OpcodeRepr::OR => {
self.gas_charge(GAS_OR)?;
self.alu_set(ra, b | c)?;
}
OpcodeRepr::ORI => {
self.gas_charge(GAS_ORI)?;
self.alu_set(ra, b | imm)?;
}
OpcodeRepr::SLL => {
self.gas_charge(GAS_SLL)?;
self.alu_set(ra, b.checked_shl(c as u32).unwrap_or_default())?;
}
OpcodeRepr::SLLI => {
self.gas_charge(GAS_SLLI)?;
self.alu_set(ra, b.checked_shl(imm as u32).unwrap_or_default())?;
}
OpcodeRepr::SRL => {
self.gas_charge(GAS_SRL)?;
self.alu_set(ra, b.checked_shr(c as u32).unwrap_or_default())?;
}
OpcodeRepr::SRLI => {
self.gas_charge(GAS_SRLI)?;
self.alu_set(ra, b.checked_shr(imm as u32).unwrap_or_default())?;
}
OpcodeRepr::SUB => {
self.gas_charge(GAS_SUB)?;
self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), c.into())?;
}
OpcodeRepr::SUBI => {
self.gas_charge(GAS_SUBI)?;
self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), imm.into())?;
}
OpcodeRepr::XOR => {
self.gas_charge(GAS_XOR)?;
self.alu_set(ra, b ^ c)?;
}
OpcodeRepr::XORI => {
self.gas_charge(GAS_XORI)?;
self.alu_set(ra, b ^ imm)?;
}
OpcodeRepr::CIMV => {
self.gas_charge(GAS_CIMV)?;
self.check_input_maturity(ra, b, c)?;
}
OpcodeRepr::CTMV => {
self.gas_charge(GAS_CTMV)?;
self.check_tx_maturity(ra, b)?;
}
OpcodeRepr::JI => {
self.gas_charge(GAS_JI)?;
self.jump(imm)?;
}
OpcodeRepr::JNEI => {
self.gas_charge(GAS_JNEI)?;
self.jump_not_equal_imm(a, b, imm)?;
}
OpcodeRepr::JNZI => {
self.gas_charge(GAS_JNZI)?;
self.jump_not_zero_imm(a, imm)?;
}
OpcodeRepr::RET => {
self.gas_charge(GAS_RET)?;
self.ret(a)?;
return Ok(ExecuteState::Return(a));
}
OpcodeRepr::RETD => {
self.gas_charge(GAS_RETD)?;
return self.ret_data(a, b).map(ExecuteState::ReturnData);
}
OpcodeRepr::RVRT => {
self.gas_charge(GAS_RVRT)?;
self.revert(a);
return Ok(ExecuteState::Revert(a));
}
OpcodeRepr::ALOC => {
self.gas_charge(GAS_ALOC)?;
self.malloc(a)?;
}
OpcodeRepr::CFEI => {
self.gas_charge(GAS_CFEI)?;
self.stack_pointer_overflow(Word::overflowing_add, imm)?;
}
OpcodeRepr::CFSI => {
self.gas_charge(GAS_CFSI)?;
self.stack_pointer_overflow(Word::overflowing_sub, imm)?;
}
OpcodeRepr::LB => {
self.gas_charge(GAS_LB)?;
self.load_byte(ra, rb, imm)?;
}
OpcodeRepr::LW => {
self.gas_charge(GAS_LW)?;
self.load_word(ra, b, imm)?;
}
OpcodeRepr::MCL => {
self.gas_charge_monad(GAS_MCL, b)?;
self.memclear(a, b)?;
}
OpcodeRepr::MCLI => {
self.gas_charge_monad(GAS_MCLI, b)?;
self.memclear(a, imm)?;
}
OpcodeRepr::MCP => {
self.gas_charge_monad(GAS_MCP, c)?;
self.memcopy(a, b, c)?;
}
OpcodeRepr::MCPI => {
self.gas_charge_monad(GAS_MCPI, imm)?;
self.memcopy(a, b, imm)?;
}
OpcodeRepr::MEQ => {
self.gas_charge(GAS_MEQ)?;
self.memeq(ra, b, c, d)?;
}
OpcodeRepr::SB => {
self.gas_charge(GAS_SB)?;
self.store_byte(a, b, imm)?;
}
OpcodeRepr::SW => {
self.gas_charge(GAS_SW)?;
self.store_word(a, b, imm)?;
}
OpcodeRepr::BAL => {
self.gas_charge(GAS_BAL)?;
self.contract_balance(ra, b, c)?;
}
OpcodeRepr::BHEI => {
self.gas_charge(GAS_BHEI)?;
self.alu_set(ra, self.block_height() as Word)?;
}
OpcodeRepr::BHSH => {
self.gas_charge(GAS_BHSH)?;
self.block_hash(a, b)?;
}
OpcodeRepr::BURN => {
self.gas_charge(GAS_BURN)?;
self.burn(a)?;
}
OpcodeRepr::CALL => {
self.gas_charge(GAS_CALL)?;
let state = self.call(a, b, c, d)?;
// raise revert state to halt execution for the callee
if let ProgramState::Revert(ra) = state {
return Ok(ExecuteState::Revert(ra));
}
}
OpcodeRepr::CB => {
self.gas_charge(GAS_CB)?;
self.block_proposer(a)?;
}
OpcodeRepr::CCP => {
self.gas_charge(GAS_CCP)?;
self.code_copy(a, b, c, d)?;
}
OpcodeRepr::CROO => {
self.gas_charge(GAS_CROO)?;
self.code_root(a, b)?;
}
OpcodeRepr::CSIZ => {
self.gas_charge(GAS_CSIZ)?;
self.code_size(ra, self.registers[rb])?;
}
OpcodeRepr::LDC => {
self.gas_charge(GAS_LDC)?;
self.load_contract_code(a, b, c)?;
}
OpcodeRepr::LOG => {
self.gas_charge(GAS_LOG)?;
self.log(a, b, c, d)?;
}
OpcodeRepr::LOGD => {
self.gas_charge(GAS_LOGD)?;
self.log_data(a, b, c, d)?;
}
OpcodeRepr::MINT => {
self.gas_charge(GAS_MINT)?;
self.mint(a)?;
}
OpcodeRepr::SRW => {
self.gas_charge(GAS_SRW)?;
self.state_read_word(ra, b)?;
}
OpcodeRepr::SRWQ => {
self.gas_charge(GAS_SRWQ)?;
self.state_read_qword(a, b)?;
}
OpcodeRepr::SWW => {
self.gas_charge(GAS_SWW)?;
self.state_write_word(a, b)?;
}
OpcodeRepr::SWWQ => {
self.gas_charge(GAS_SWWQ)?;
self.state_write_qword(a, b)?;
}
OpcodeRepr::ECR => {
self.gas_charge(GAS_ECR)?;
self.ecrecover(a, b, c)?;
}
OpcodeRepr::K256 => {
self.gas_charge(GAS_K256)?;
self.keccak256(a, b, c)?;
}
OpcodeRepr::S256 => {
self.gas_charge(GAS_S256)?;
self.sha256(a, b, c)?;
}
OpcodeRepr::XIL => {
self.gas_charge(GAS_XIL)?;
self.transaction_input_length(ra, b)?;
}
OpcodeRepr::XIS => {
self.gas_charge(GAS_XIS)?;
self.transaction_input_start(ra, b)?;
}
OpcodeRepr::XOL => {
self.gas_charge(GAS_XOL)?;
self.transaction_output_length(ra, b)?;
}
OpcodeRepr::XOS => {
self.gas_charge(GAS_XOS)?;
self.transaction_output_start(ra, b)?;
}
OpcodeRepr::XWL => {
self.gas_charge(GAS_XWL)?;
self.transaction_witness_length(ra, b)?;
}
OpcodeRepr::XWS => {
self.gas_charge(GAS_XWS)?;
self.transaction_witness_start(ra, b)?;
}
OpcodeRepr::FLAG => {
self.gas_charge(GAS_FLAG)?;
self.set_flag(a)?;
}
OpcodeRepr::GM => {
self.gas_charge(GAS_GM)?;
self.metadata(ra, imm as Immediate18)?;
}
OpcodeRepr::TR => {
self.gas_charge(GAS_TR)?;
self.transfer(a, b, c)?;
}
OpcodeRepr::TRO => {
self.gas_charge(GAS_TRO)?;
self.transfer_output(a, b, c, d)?;
}
// list of currently unimplemented opcodes
OpcodeRepr::SLDC | _ => {
return Err(PanicReason::ErrorFlag.into());
}
}
Ok(ExecuteState::Proceed)
}

One alternative is to use macros to avoid code duplication, but this compromises code debug since macros are usually not expanded in IDEs.

Other option is to create a single function that will match for regular instructions, and branch for contract opcodes to check the context of the execution. This is not desirable too because it would increase the computation cost of the entirety of the execution.

We need to analyze the rationale of these options and come with the best solution for this tech-debt.

`GM` opcode bug

Given the following contract, the example test is passing. It should not be, as the assembly block which calls gm should return true in this case.

As noted by @adlerjohn on slack:

If used from a contract that was called directly from a script, then
Set $rA to true if parent is an external context, false otherwise.

ref: https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/opcodes.md#gm-get-metadata

Contract

contract;

abi AuthTesting {
    fn is_caller_external(gas_: u64, amount_: u64, color_: b256, value: bool) -> bool;
}

impl AuthTesting for Contract {
    fn is_caller_external(gas_: u64, amount_: u64, color_: b256, value: bool) -> bool {
        asm(r1) {
            gm r1 i1;
            r1: bool
        }
    }
}

Test:

#[tokio::test]
async fn is_external_from_sdk() {
    abigen!(AuthContract, "test_artifacts/auth_testing_contract/src/abi-output.json");
    let salt = Salt::from([0u8; 32]);
    let compiled = Contract::compile_sway_contract("test_artifacts/auth_testing_contract", salt).unwrap();
    let (client, auth_id) = Contract::launch_and_deploy(&compiled).await.unwrap();
    let auth_instance = AuthContract::new(compiled, client);

    let result = auth_instance
        .is_caller_external(true)
        .call()
        .await
        .unwrap();

    assert_eq!(result.value, false);
}

Test memory boundaries for opcode execution

We are to use words as pairs of encoded instructions.

However, jump instructions are half-word. And we also might interrupt a program (e.g. debug) in a half-word

This means that incrementing by word might lead to invalid memory access in case the code is in the memory boundary.

We need to have a test case covering that - and, if applicable, fix the code so no panic occur.

Add serde for `Error`

serde implementation for the error type is currently not working. It should be included, but serde isn't implemented for std::io::Error - therefore, some research to find an optimal solution will be required.

The broken serde implementation for the error type was removed in #115

#[cfg_attr(feature = "serde-types-minimal", derive(serde::Serialize, serde::Deserialize))]

Bundle Tx execution on SubState with DB binding

When creating a new block or when importing a block from the network we need to have the ability to execute all transactions in some temporary state. Check if outputs/storages/hashes/merkletriees matches what is given, or in case of a new block, they need to be created. And then apply that temporary state to the database, or for new block send it to consensus for signing.

Maybe call it SubState? (name pending)

SubState should have its own changed state and in case if a state item (Storage/utxo) is not found it should have the ability to fetch it from DB.
It should probably contain the malleable list of executed transactions.

Extract cryptography-related helpers into an external crate

Downstream crates, such as fuels-rs started to rely on cryptographic functions that live under fuel-vm such as secp256k1_sign_compact_recoverable() and secp256k1_sign_compact_recover() in scr/crypto.rs.

Since they're not exclusive to the FuelVM and will be used in other places, to avoid (1) implementation of cryptographic functions and (2) depending on the FuelVM for those, we should extract these and other cryptographic-related functions into another, lighter weight crate.

Externalize Validity Checks

Currently the VM performs several validity checks on a transaction in the init/run methods. This includes things like:

  • Checking the input vs output amounts
  • Verifying predicates (currently only done for create txs)
  • Verifying contract outputs have corresponding inputs

Along with many other checks. Since these are internalized to the VM, the only way for the client to perform all these internal validations is to dry-run each tx. While feasible, it may be better if VM provided an interface for purely validating txs, which could be reused in many places such as both tx pool submission or block execution. This would make it easier to prevent blocks with invalid / unspendable utxos from ever being constructed.

Consume `fuel-merkle` for `crypto::ephemeral_merkle_root`

We have a in-memory implementation of the binary merkle tree in

pub fn ephemeral_merkle_root<L, I>(mut leaves: I) -> Bytes32

To avoid code duplication, its better to consume the fuel-merkle implementation when a memory backend for cheap trees is available.

For that, we either use fuel-merkle itself as a dependency of fuel-vm, or a dedicated repo.

Add log `tracing`

The consumers of fuel-vm use tracing as log mechanism

We should add tracing with DEBUG and TRACE levels to track the VM execution

Use the following criteria:

  • after any mutation, log with debug
  • upon any request, log with trace

Predicate Verification

Perform predicate verification for:

  1. Create Transactions
  2. Script Transactions

Ensure predicates can be validated without running the entire transaction.

  • ensure predicate execution doesn't consume gas
  • reset registers between predicates
  • ensure PC doesn't jump backward or outside of predicate code

Reassess the nature of the heap and `$hp`.

The heap and ALOC haven't been used at all in the Sway compiler or runtime until recently and I've noticed what I think is a slight incongruity in the way $hp is treated.

This is assuming what I think is the case (by testing the VM via Sway/ASM and by looking briefly at memory.rs) that $hp holds the address of the last available byte not in the heap, or the end of the memory between the stack and heap.

It is initialised to VM_MAX_RAM - 1 which asserts this idea. And in my experience if you try to read from $hp you'll get a memory overflow panic. Reading from $hp + 1 is fine (provided it's below VM_MAX_RAM).

This is a bit weird. I think $hp should point to the first byte in the allocated heap. It should therefore be initialised to VM_MAX_RAM -- I've suggested as much in FuelLabs/fuel-specs#322.

Then the use of $sp & CFEI/CFSI and $hp & ALOC would be symmetric, just in different directions.

To allocate 16 bytes on the stack, result in r0:

move r0 sp
cfei 16

To allocate r1 bytes on the heap, result in r0 currently:

aloc r1
addi r0 hp 1  ; Weird. :)

To allocate r1 bytes on the heap, result in r0 after proposed adjustment:

aloc r1
move r0 hp

After writing all this down and reading back I know it's a pretty subtle difference and not exactly high priority, but when coming across this behaviour initially I was really confused. Using an inclusive range for free memory isn't usually how it's done -- usually end will point to the first invalid item rather than the last valid one.

Unclear output when using logical shift opcodes

While trying to use the sll opcode, I'm seeing output that doesn't match my expectations.
For my use case, I want to use the sll opcode, and recover any overflow value placed in $of. To do so I've created a function shift_left_with_overflow() which returns both the shifted value and the value of the $of register from an asm block.
The following example script can be run to observe what is currently happening, and the flags can be modified to test different behaviour.
My assumption was that using the flag 2 opcode would allow the overflow to be placed in $of rather than causing a VM panic, but atm it seems that this logic is not yet implemented (meaning overflows never cause panics, the overflow is sometimes places in $of, and the shifted value sometimes demonstrates wrapping.

Note: I've not tested srl but it will be simple to do so by modifying the shift opcode in the asm block.

script;

use std::assert::assert;
use std::chain::log_u64;

const FLAG= 2;

pub fn shift_left_with_overflow(word: u64, shift_amount: u64) -> (u64, u64) {
    let mut output = (0, 0);
    let (shifted, overflow) = asm(out: output, r1: word, r2: shift_amount, r3, r4: FLAG) {
       flag r4;        // set the flag to allow overflow without panic
       sll r3 r1 r2;   // shift 'word' left 'shift_amount' and put result in r3
       sw out r3 i0;   // store the word at r4 in output + 0 bytes
       sw out of i1;   // store the word at r4 in output + 0 bytes
       out: (u64, u64) // return both values
    };

    (shifted, overflow)
}


fn main() -> bool {

    let max_u64 = 18_446_744_073_709_551_615;

    let (shifted, overflow) = shift_left_with_overflow(1, 1);
    // log_u64(shifted);
    // log_u64(overflow);
    assert(shifted == 2);
    assert(overflow == 0);

    let (shifted_2, overflow_2) = shift_left_with_overflow(max_u64, 1);
    assert(shifted_2 == max_u64 - 1);
    assert(overflow_2 == 0); // I would expect this to be 1 !?

    let (shifted_3, overflow_3) = shift_left_with_overflow(1, 63);
    assert(shifted_3 == 9223372036854775808); // (1*2^63)
    assert(overflow_3 == 0);

    let (shifted_4, overflow_4) = shift_left_with_overflow(1, 64);
    assert(shifted_4 == 1); // I would expect this to be 0 !?
    assert(overflow_4 == 1);

    let (shifted_5, overflow_5) = shift_left_with_overflow(9223372036854775808, 1);
    assert(shifted_5 == 0);
    assert(overflow_5 == 0); // I would expect this to be 1 !?

    true // currently returning true
}

Error when executing opcode: better error messages?

The UX around a failing contract call is rough. The same error, Failed to execute opcode CALL(...), is thrown in all of the following cases:

  1. not enough gas forwarded
  2. too much gas forwarded
  3. Contract not in TX inputs
  4. Contract in inputs not in TX outputs
  5. probably more things

Would it be possible for the log message to tell the user a bit more about what went wrong?

Return `gas_used` from `transact(tx)` API

Fuel-core needs a way to easily extract and store the amount of gas actually consumed by executing a transaction. While this may be available in a register, it would be helpful if it was included in the returned StateTransitionRef or some other more direct way.

Optimize consumed gas calculation

The gas charge function is designed in a way to make it possible to use constants for most of opcodes that charge gas based on constants, and use callbacks for opcodes with variable gas cost (example: ALOC).

The execute function must be refactored to reduce the match count to one and then benefit of constant gas cost calculation.

replace TxBuilder in VM with fuel-tx

There is a tx builder in fuel-vm, which has been largely superseded by the one provided by fuel-tx. Remove the fuel-vm version and standardize around the fuel-tx tx builder.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.