Skip to content

Commit

Permalink
Merge branch 'main' of github.com:lambdaclass/lambda_ethereum_rust in…
Browse files Browse the repository at this point in the history
…to trie_iter
  • Loading branch information
fmoletta committed Oct 29, 2024
2 parents c69690d + ac7559a commit 65f8c77
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ TEST_PATTERN ?= /
# The endpoints tested may be limited by supplying a test pattern in the form "/endpoint_1|enpoint_2|..|enpoint_n"
# For example, to run the rpc-compat suites for eth_chainId & eth_blockNumber you should run:
# `make run-hive SIMULATION=ethereum/rpc-compat TEST_PATTERN="/eth_chainId|eth_blockNumber"`
run-hive: build-image setup-hive ## 🧪 Run Hive testing suite
run-hive: build-image ## 🧪 Run Hive testing suite
cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)"

run-hive-debug: build-image setup-hive ## 🐞 Run Hive testing suite in debug mode
run-hive-debug: build-image ## 🐞 Run Hive testing suite in debug mode
cd hive && ./hive --sim $(SIMULATION) --client ethereumrust --sim.limit "$(TEST_PATTERN)" --docker.output

clean-hive-logs: ## 🧹 Clean Hive logs
Expand Down
2 changes: 1 addition & 1 deletion crates/l2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ down: down-local-l1 down-l2 ## 🛑 Shuts down the localnet

clean: clean-contract-deps ## 🧹 Cleans the localnet

restart: restart-local-l1 restart-contract-deps deploy-l1 restart-l2 ## 🔄 Restarts the localnet
restart: restart-local-l1 deploy-l1 restart-l2 ## 🔄 Restarts the localnet

cli: ## 🛠️ Installs the L2 Lambda Ethereum Rust CLI
cargo install --path ${ETHEREUM_RUST_PATH}/cmd/ethereum_rust_l2/ --force
Expand Down
2 changes: 2 additions & 0 deletions crates/vm/levm/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
*.tar.gz

tests/ef_testcases
2 changes: 2 additions & 0 deletions crates/vm/levm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ pub const TX_BASE_COST: U256 = U256([21000, 0, 0, 0]);
pub const MAX_CODE_SIZE: usize = 0x6000;
pub const MAX_CREATE_CODE_SIZE: usize = 2 * MAX_CODE_SIZE;

pub const INVALID_CONTRACT_PREFIX: u8 = 0xef;

// Costs in gas for init word and init code (in wei)
pub const INIT_WORD_COST: i64 = 2;

Expand Down
6 changes: 5 additions & 1 deletion crates/vm/levm/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ pub enum VMError {
MissingBlobHashes,
BlobHashIndexOutOfBounds,
RevertOpcode,
SenderAccountDoesNotExist,
AddressDoesNotMatchAnAccount,
SenderAccountShouldNotHaveBytecode,
SenderBalanceShouldContainTransferValue,
GasPriceIsLowerThanBaseFee,
AddressAlreadyOccupied,
ContractOutputTooBig,
InvalidInitialByte,
NonceOverflow,
}

pub enum OpcodeSuccess {
Expand Down
9 changes: 6 additions & 3 deletions crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256
),
];

let state = Db {
let mut state = Db {
accounts: accounts.into(),
block_hashes: Default::default(),
};
Expand All @@ -56,7 +56,7 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256
// add the account passed by parameter

VM::new(
Address::from_low_u64_be(42),
Some(Address::from_low_u64_be(42)),
address,
Default::default(),
Default::default(),
Expand All @@ -68,9 +68,12 @@ pub fn new_vm_with_ops_addr_bal(bytecode: Bytes, address: Address, balance: U256
U256::one(),
Default::default(),
Default::default(),
state,
&mut state,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
None,
)
.unwrap()
}
217 changes: 200 additions & 17 deletions crates/vm/levm/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,8 @@ pub fn word_to_address(word: U256) -> Address {
}

impl VM {
// TODO: Refactor this.
#[allow(clippy::too_many_arguments)]
pub fn new(
fn call_type_transaction(
to: Address,
msg_sender: Address,
value: U256,
Expand All @@ -220,23 +219,13 @@ impl VM {
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
) -> Self {
// TODO: This handles only CALL transactions.
) -> Result<VM, VMError> {
let bytecode = db.get_account_bytecode(&to);

// TODO: This handles only CALL transactions.
// TODO: Remove this allow when CREATE is implemented.
#[allow(clippy::redundant_locals)]
let to = to;

// TODO: In CALL this is the `to`, in CREATE it is not.
let code_addr = to;

// TODO: this is mostly placeholder
let initial_call_frame = CallFrame::new(
msg_sender,
to,
code_addr,
to,
None,
bytecode,
value,
Expand Down Expand Up @@ -264,11 +253,204 @@ impl VM {
tx_blob_hashes,
};

Self {
Ok(VM {
call_frames: vec![initial_call_frame],
db,
env,
accrued_substate: Substate::default(),
})
}

// Functionality should be:
// (1) Check whether caller has enough balance to make a transfer
// (2) Derive the new contract’s address from the caller’s address (passing in the creator account’s nonce)
// (3) Create the new contract account using the derived contract address (changing the “world state” StateDB)
// (4) Transfer the initial Ether endowment from caller to the new contract
// (5) Set input data as contract’s deploy code, then execute it with EVM. The ret variable is the returned contract code
// (6) Check for error. Or if the contract code is too big, fail. Charge the user gas then set the contract code
// Source: https://medium.com/@hayeah/diving-into-the-ethereum-vm-part-5-the-smart-contract-creation-process-cb7b6133b855
#[allow(clippy::too_many_arguments)]
fn create_type_transaction(
sender: Address,
secret_key: H256,
db: &mut Db,
value: U256,
calldata: Bytes,
block_number: U256,
coinbase: Address,
timestamp: U256,
prev_randao: Option<H256>,
chain_id: U256,
base_fee_per_gas: U256,
gas_price: U256,
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
salt: Option<U256>,
) -> Result<VM, VMError> {
let mut db_copy = db.clone();
let mut sender_account = match db_copy.accounts.get(&sender) {
Some(acc) => acc,
None => {
return Err(VMError::OutOfGas);
}
}
.clone();

// (1)
if sender_account.balance < value {
return Err(VMError::OutOfGas); // Maybe a more personalized error
}

sender_account.nonce = sender_account
.nonce
.checked_add(1)
.ok_or(VMError::NonceOverflow)?;

// (2)
let new_contract_address = match salt {
Some(salt) => VM::calculate_create2_address(sender, &calldata, salt),
None => VM::calculate_create_address(sender, sender_account.nonce),
};

// If address is already in db, there's an error
if db_copy.accounts.contains_key(&new_contract_address) {
return Err(VMError::AddressAlreadyOccupied);
}

// (3)
let mut created_contract = Account::new(
new_contract_address,
value,
calldata.clone(),
1,
Default::default(),
);
db_copy.add_account(new_contract_address, created_contract.clone());

// (4)
sender_account.balance -= value;
created_contract.balance += value;

// (5)
let code: Bytes = calldata.clone();

// Call the contract
let mut vm = VM::new(
Some(created_contract.address),
sender,
value,
code,
sender_account.balance,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
&mut db_copy,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
secret_key,
None,
)?;

let res = vm.transact()?;
// Don't use a revert bc work with clones, so don't have to save previous state

let contract_code = res.output;

// (6)
if contract_code.len() > MAX_CODE_SIZE {
return Err(VMError::ContractOutputTooBig);
}
// Supposing contract code has contents
if contract_code[0] == INVALID_CONTRACT_PREFIX {
return Err(VMError::InvalidInitialByte);
}

// If the initialization code completes successfully, a final contract-creation cost is paid,
// the code-deposit cost, c, proportional to the size of the created contract’s code
let creation_cost = 200 * contract_code.len();

sender_account.balance = sender_account
.balance
.checked_sub(U256::from(creation_cost))
.ok_or(VMError::OutOfGas)?;

created_contract.bytecode = contract_code;

let mut acc = db_copy.accounts.get_mut(&sender).unwrap();
*acc = sender_account;
acc = db_copy.accounts.get_mut(&new_contract_address).unwrap();
*acc = created_contract;

*db = db_copy;
Ok(vm)
}

// TODO: Refactor this.
#[allow(clippy::too_many_arguments)]
pub fn new(
to: Option<Address>,
msg_sender: Address,
value: U256,
calldata: Bytes,
gas_limit: U256,
block_number: U256,
coinbase: Address,
timestamp: U256,
prev_randao: Option<H256>,
chain_id: U256,
base_fee_per_gas: U256,
gas_price: U256,
db: &mut Db,
block_blob_gas_used: Option<U256>,
block_excess_blob_gas: Option<U256>,
tx_blob_hashes: Option<Vec<H256>>,
secret_key: H256,
salt: Option<U256>,
) -> Result<Self, VMError> {
// Maybe this desicion should be made in an upper layer
match to {
Some(address) => VM::call_type_transaction(
address,
msg_sender,
value,
calldata,
gas_limit,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
db.clone(),
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
),
None => VM::create_type_transaction(
msg_sender,
secret_key,
db,
value,
calldata,
block_number,
coinbase,
timestamp,
prev_randao,
chain_id,
base_fee_per_gas,
gas_price,
block_blob_gas_used,
block_excess_blob_gas,
tx_blob_hashes,
salt,
),
}
}

Expand Down Expand Up @@ -415,7 +597,6 @@ impl VM {
}
}

// let account = self.db.accounts.get(&self.env.origin).unwrap();
/// Based on Ethereum yellow paper's initial tests of intrinsic validity (Section 6). The last version is
/// Shanghai, so there are probably missing Cancun validations. The intrinsic validations are:
///
Expand All @@ -437,7 +618,9 @@ impl VM {
// Validations (1), (2), (3), (5), and (8) are assumed done in upper layers.
let sender_account = match self.db.accounts.get(&self.env.origin) {
Some(acc) => acc,
None => return Err(VMError::SenderAccountDoesNotExist),
None => return Err(VMError::AddressDoesNotMatchAnAccount),
// This is a check for completeness. However if it were a none and
// it was not caught it would be caught in clause 6.
};
// (4)
if sender_account.has_code() {
Expand Down
Loading

0 comments on commit 65f8c77

Please sign in to comment.