Skip to content

Commit

Permalink
Implement Create Tx Storage Slot Initialization (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
Voxelot authored Feb 4, 2022
1 parent 3b08945 commit 5347cd1
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 46 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dyn-clone = { version = "1.0", optional = true }
fuel-asm = "0.1"
fuel-merkle = "0.1"
fuel-storage = "0.1"
fuel-tx = "0.3"
fuel-tx = "0.5"
fuel-types = "0.1"
itertools = "0.10"
secp256k1 = { version = "0.20", features = ["recovery"] }
Expand All @@ -25,7 +25,7 @@ tracing = "0.1"
rand = { version = "0.8", optional = true }

[dev-dependencies]
fuel-tx = { version = "0.3", features = ["random"] }
fuel-tx = { version = "0.5", features = ["random"] }
fuel-vm = { path = ".", default-features = false, features = ["test-helpers"]}

[features]
Expand Down
24 changes: 21 additions & 3 deletions src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::crypto;
use crate::error::InterpreterError;

use fuel_tx::crypto::Hasher;
use fuel_tx::{Transaction, ValidationError};
use fuel_tx::{StorageSlot, Transaction, ValidationError};
use fuel_types::{Bytes32, ContractId, Salt};

use std::cmp;
Expand All @@ -31,15 +31,33 @@ impl Contract {
crypto::ephemeral_merkle_root(root)
}

/// Calculate and return the contract id, provided a salt and a code root.
/// Calculate the root of the initial storage slots for this contract
/// TODO: Use a sparse merkle tree once the implementation is available
pub fn initial_state_root(storage_slots: &[StorageSlot]) -> Bytes32 {
let leaves = storage_slots.iter().map(|slot| {
let mut buf = [0u8; 64];
buf[..32].copy_from_slice(slot.key().as_slice());
buf[32..].copy_from_slice(slot.value().as_slice());
buf
});
crypto::ephemeral_merkle_root(leaves)
}

/// The default state root value without any entries
pub fn default_state_root() -> Bytes32 {
Self::initial_state_root(&[])
}

/// Calculate and return the contract id, provided a salt, code root and state root.
///
/// <https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/identifiers.md#contract-id>
pub fn id(&self, salt: &Salt, root: &Bytes32) -> ContractId {
pub fn id(&self, salt: &Salt, root: &Bytes32, state_root: &Bytes32) -> ContractId {
let mut hasher = Hasher::default();

hasher.input(ContractId::SEED);
hasher.input(salt);
hasher.input(root);
hasher.input(state_root);

ContractId::from(*hasher.digest())
}
Expand Down
16 changes: 13 additions & 3 deletions src/interpreter/executors/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ where

match &self.tx {
Transaction::Create {
salt, static_contracts, ..
salt,
static_contracts,
storage_slots,
..
} => {
if static_contracts
.iter()
Expand All @@ -33,13 +36,14 @@ where

let contract = Contract::try_from(&self.tx)?;
let root = contract.root();
let id = contract.id(salt, &root);
let storage_root = Contract::initial_state_root(storage_slots);
let id = contract.id(salt, &root, &storage_root);

if !&self
.tx
.outputs()
.iter()
.any(|output| matches!(output, Output::ContractCreated { contract_id } if contract_id == &id))
.any(|output| matches!(output, Output::ContractCreated { contract_id, state_root } if contract_id == &id && state_root == &storage_root))
{
return Err(InterpreterError::Panic(PanicReason::ContractNotInInputs));
}
Expand All @@ -52,6 +56,12 @@ where
.storage_contract_root_insert(&id, salt, &root)
.map_err(InterpreterError::from_io)?;

for storage_slot in storage_slots {
self.storage
.merkle_contract_state_insert(&id, storage_slot.key(), storage_slot.value())
.map_err(InterpreterError::from_io)?;
}

// Verify predicates
// https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/tx_validity.md#predicate-verification
// TODO this should be abstracted with the client
Expand Down
14 changes: 11 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub mod test_helpers {
use crate::prelude::{InterpreterStorage, MemoryClient, MemoryStorage, Transactor};
use crate::state::StateTransition;
use fuel_asm::Opcode;
use fuel_tx::{Input, Output, Transaction, Witness};
use fuel_tx::{Input, Output, StorageSlot, Transaction, Witness};
use fuel_types::{Color, ContractId, Salt, Word};
use itertools::Itertools;
use rand::prelude::StdRng;
Expand Down Expand Up @@ -237,12 +237,19 @@ pub mod test_helpers {
&mut self,
contract: Vec<Opcode>,
initial_balance: Option<(Color, Word)>,
initial_state: Option<Vec<StorageSlot>>,
) -> CreatedContract {
let storage_slots = if let Some(slots) = initial_state {
slots
} else {
Default::default()
};
let salt: Salt = self.rng.gen();
let program: Witness = contract.iter().copied().collect::<Vec<u8>>().into();
let storage_root = crate::contract::Contract::initial_state_root(&storage_slots);
let contract = crate::contract::Contract::from(program.as_ref());
let contract_root = contract.root();
let contract_id = contract.id(&salt, &contract_root);
let contract_id = contract.id(&salt, &contract_root, &storage_root);

let tx = Transaction::create(
self.gas_price,
Expand All @@ -252,8 +259,9 @@ pub mod test_helpers {
0,
salt,
vec![],
storage_slots,
vec![],
vec![Output::contract_created(contract_id)],
vec![Output::contract_created(contract_id, storage_root)],
vec![program],
);

Expand Down
12 changes: 8 additions & 4 deletions tests/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ fn backtrace() {

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract_undefined = contract.id(&salt, &contract_root);
let state_root = Contract::default_state_root();
let contract_undefined = contract.id(&salt, &contract_root, &state_root);

let output = Output::contract_created(contract_undefined);
let output = Output::contract_created(contract_undefined, state_root);

let bytecode_witness = 0;
let tx_deploy = Transaction::create(
Expand All @@ -42,6 +43,7 @@ fn backtrace() {
salt,
vec![],
vec![],
vec![],
vec![output],
vec![program],
);
Expand Down Expand Up @@ -69,9 +71,10 @@ fn backtrace() {

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract_call = contract.id(&salt, &contract_root);
let state_root = Contract::default_state_root();
let contract_call = contract.id(&salt, &contract_root, &state_root);

let output = Output::contract_created(contract_call);
let output = Output::contract_created(contract_call, state_root);

let bytecode_witness = 0;
let tx_deploy = Transaction::create(
Expand All @@ -83,6 +86,7 @@ fn backtrace() {
salt,
vec![],
vec![],
vec![],
vec![output],
vec![program],
);
Expand Down
81 changes: 77 additions & 4 deletions tests/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use fuel_vm::prelude::*;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};

use fuel_tx::StorageSlot;
use fuel_vm::script_with_data_offset;
use fuel_vm::util::test_helpers::TestBuilder;
use std::mem;

const WORD_SIZE: usize = mem::size_of::<Word>();
Expand Down Expand Up @@ -106,9 +109,10 @@ fn state_read_write() {

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract = contract.id(&salt, &contract_root);
let state_root = Contract::default_state_root();
let contract = contract.id(&salt, &contract_root, &state_root);

let output = Output::contract_created(contract);
let output = Output::contract_created(contract, state_root);

let bytecode_witness = 0;
let tx_deploy = Transaction::create(
Expand All @@ -120,6 +124,7 @@ fn state_read_write() {
salt,
vec![],
vec![],
vec![],
vec![output],
vec![program],
);
Expand Down Expand Up @@ -294,10 +299,11 @@ fn load_external_contract_code() {

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract_id = contract.id(&salt, &contract_root);
let state_root = Contract::default_state_root();
let contract_id = contract.id(&salt, &contract_root, &state_root);

let input0 = Input::contract(rng.gen(), rng.gen(), rng.gen(), contract_id);
let output0 = Output::contract_created(contract_id);
let output0 = Output::contract_created(contract_id, state_root);
let output1 = Output::contract(0, rng.gen(), rng.gen());

let tx_create_target = Transaction::create(
Expand All @@ -309,6 +315,7 @@ fn load_external_contract_code() {
salt,
vec![],
vec![],
vec![],
vec![output0],
vec![program.clone()],
);
Expand Down Expand Up @@ -396,3 +403,69 @@ fn load_external_contract_code() {
panic!("Script did not return a value");
}
}

#[test]
fn can_read_state_from_initial_storage_slots() {
// the initial key and value pair for the contract
let key = Hasher::hash(b"initial key");
let value = [128u8; 32].into();

let program = vec![
// load memory location of reference to key
Opcode::ADDI(0x10, REG_FP, CallFrame::a_offset() as Immediate12),
// deref key memory location from script data to 0x10
Opcode::LW(0x10, 0x10, 0),
// alloc 32 bytes stack space
Opcode::ADDI(0x11, REG_SP, 0),
Opcode::CFEI(32 as Immediate24),
// load state value to stack
Opcode::SRWQ(0x11, 0x10),
// log value
Opcode::ADDI(0x12, REG_ZERO, 32 as Immediate12),
Opcode::LOGD(REG_ZERO, REG_ZERO, 0x11, 0x12),
Opcode::RET(REG_ONE),
];

let init_storage = vec![StorageSlot::new(key, value)];

let gas_limit = 1_000_000;
let mut builder = TestBuilder::new(2023u64);
let contract = builder.setup_contract(program, None, Some(init_storage)).contract_id;

let (script, offset) = script_with_data_offset!(
data_offset,
vec![
// load position of call arguments from script data
Opcode::ADDI(0x10, REG_ZERO, data_offset + 32),
// load gas limit
Opcode::ADDI(0x11, REG_ZERO, gas_limit as Immediate12),
Opcode::CALL(0x10, REG_ZERO, REG_ZERO, 0x11),
Opcode::RET(REG_ONE),
]
);

let script_data: Vec<u8> = [
key.as_ref(),
Call::new(contract, offset as Word, 0).to_bytes().as_slice(),
]
.into_iter()
.flatten()
.copied()
.collect();

let log_tx = builder
.gas_limit(gas_limit)
.gas_price(0)
.byte_price(0)
.contract_input(contract)
.contract_output(&contract)
.script(script)
.script_data(script_data)
.execute();

for receipt in log_tx.receipts() {
if let Receipt::LogData { data, .. } = receipt {
assert_eq!(data.as_slice(), value.as_slice())
}
}
}
14 changes: 8 additions & 6 deletions tests/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ fn mint_burn() {

let contract = Contract::from(program.as_ref());
let contract_root = contract.root();
let contract = contract.id(&salt, &contract_root);
let state_root = Contract::default_state_root();
let contract = contract.id(&salt, &contract_root, &state_root);

let color = Color::from(*contract);
let output = Output::contract_created(contract);
let output = Output::contract_created(contract, state_root);

let bytecode_witness = 0;
let tx = Transaction::create(
Expand All @@ -52,6 +53,7 @@ fn mint_burn() {
salt,
vec![],
vec![],
vec![],
vec![output],
vec![program],
);
Expand Down Expand Up @@ -233,7 +235,7 @@ fn internal_transfer_reduces_source_contract_balance_and_increases_destination_c
let initial_internal_balance = 1_000_000;

let mut test_context = TestBuilder::new(2322u64);
let dest_contract_id = test_context.setup_contract(vec![], None).contract_id;
let dest_contract_id = test_context.setup_contract(vec![], None, None).contract_id;

let program = vec![
// load amount of tokens
Expand All @@ -248,7 +250,7 @@ fn internal_transfer_reduces_source_contract_balance_and_increases_destination_c
Opcode::RET(REG_ONE),
];
let sender_contract_id = test_context
.setup_contract(program, Some((asset_id, initial_internal_balance)))
.setup_contract(program, Some((asset_id, initial_internal_balance)), None)
.contract_id;

let (script_ops, offset) = script_with_data_offset!(
Expand Down Expand Up @@ -315,7 +317,7 @@ fn internal_transfer_cant_exceed_more_than_source_contract_balance() {
let initial_internal_balance = 100;

let mut test_context = TestBuilder::new(2322u64);
let dest_contract_id = test_context.setup_contract(vec![], None).contract_id;
let dest_contract_id = test_context.setup_contract(vec![], None, None).contract_id;

let program = vec![
// load amount of tokens
Expand All @@ -331,7 +333,7 @@ fn internal_transfer_cant_exceed_more_than_source_contract_balance() {
];

let sender_contract_id = test_context
.setup_contract(program, Some((asset_id, initial_internal_balance)))
.setup_contract(program, Some((asset_id, initial_internal_balance)), None)
.contract_id;

let (script_ops, offset) = script_with_data_offset!(
Expand Down
Loading

0 comments on commit 5347cd1

Please sign in to comment.