diff --git a/Cargo.toml b/Cargo.toml index b6f67c41f49..b5b3b02d762 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,7 +73,7 @@ edition = "2021" [workspace.dependencies] alloy = { version = "1.0.5", default-features = false } -alloy-primitives = { version = "1.1.0", default-features = false, features = [ +alloy-primitives = { version = "1.3.0", default-features = false, features = [ "serde", "k256", ] } diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 0a51cb035df..0d7fa8571ef 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5189fa9a8797e92396bc4b4454c5f2073a4945f7c2b366af9af60f9536558f7a" +checksum = "459f98c6843f208856f338bfb25e65325467f7aff35dfeb0484d0a76e059134b" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -357,9 +357,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c35fc4b03ace65001676358ffbbaefe2a2b27ee50fe777c345082c7c888be8" +checksum = "3cfebde8c581a5d37b678d0a48a32decb51efd7a63a08ce2517ddec26db705c8" dependencies = [ "alloy-rlp", "bytes", @@ -569,9 +569,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60fcfa26956bcb22f66ab13407115197f26ef23abca5b48d39a1946897382d74" +checksum = "aedac07a10d4c2027817a43cc1f038313fc53c7ac866f7363239971fd01f9f18" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", @@ -583,9 +583,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a9b402f0013f1ff8c24066eeafc2207a8e52810a2b18b77776ce7fead5af41" +checksum = "24f9a598f010f048d8b8226492b6401104f5a5c1273c2869b72af29b48bb4ba9" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", @@ -602,9 +602,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d02d61741337bb6b3f4899c2e3173fe17ffa2810e143d3b28acd953197c8dd79" +checksum = "f494adf9d60e49aa6ce26dfd42c7417aa6d4343cf2ae621f20e4d92a5ad07d85" dependencies = [ "alloy-json-abi", "const-hex", @@ -620,9 +620,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251273c5aa1abb590852f795c938730fa641832fc8fa77b5478ed1bf11b6097e" +checksum = "52db32fbd35a9c0c0e538b58b81ebbae08a51be029e7ad60e08b60481c2ec6c3" dependencies = [ "serde", "winnow 0.7.10", @@ -630,14 +630,13 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02635bce18205ff8149fb752c753b0a91ea3f3c8ee04c58846448be4811a640" +checksum = "a285b46e3e0c177887028278f04cc8262b76fd3b8e0e20e93cea0a58c35f5ac5" dependencies = [ "alloy-json-abi", "alloy-primitives", "alloy-sol-macro", - "const-hex", "serde", ] @@ -6084,9 +6083,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.0.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34c9c96de1f835488c1501092847b522be88c9ac6fb0d4c0fbea92992324c8f4" +checksum = "a7a985ff4ffd7373e10e0fb048110fb11a162e5a4c47f92ddb8787a6f766b769" dependencies = [ "paste", "proc-macro2", diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5fc7d2683c5..e5e4fc1b4ef 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -20,7 +20,7 @@ members = [ ] [workspace.dependencies] -alloy-primitives = { version = "1.0.0", default-features = false } +alloy-primitives = { version = "1.3.0", default-features = false } alloy-sol-types = { version = "1.0.0", default-features = false } anyhow = "1.0.80" assert_matches = "1.5.0" diff --git a/examples/call-evm-counter/src/contract.rs b/examples/call-evm-counter/src/contract.rs index 1c6ae84f4c7..bf24684d73b 100644 --- a/examples/call-evm-counter/src/contract.rs +++ b/examples/call-evm-counter/src/contract.rs @@ -8,7 +8,7 @@ use alloy_sol_types::{sol, SolCall}; use call_evm_counter::{CallCounterAbi, CallCounterOperation}; use linera_sdk::{ abis::evm::EvmAbi, - linera_base_types::{ApplicationId, WithContractAbi}, + linera_base_types::{Amount, ApplicationId, EvmOperation, WithContractAbi}, Contract, ContractRuntime, }; @@ -56,13 +56,15 @@ impl Contract for CallCounterContract { match operation { CallCounterOperation::Increment(increment) => { let operation = incrementCall { input: increment }; - let operation = operation.abi_encode(); + let operation = EvmOperation::new(Amount::ZERO, operation.abi_encode()); + let operation = operation.to_bytes().unwrap(); self.process_operation(operation) } CallCounterOperation::TestCallAddress => { let remote_address = address!("0000000000000000000000000000000000000000"); let operation = call_from_wasmCall { remote_address }; - let operation = operation.abi_encode(); + let operation = EvmOperation::new(Amount::ZERO, operation.abi_encode()); + let operation = operation.to_bytes().unwrap(); self.process_operation(operation) } } diff --git a/linera-base/src/data_types.rs b/linera-base/src/data_types.rs index cafe440e8cc..b13448dbae1 100644 --- a/linera-base/src/data_types.rs +++ b/linera-base/src/data_types.rs @@ -87,6 +87,20 @@ impl From for U256 { } } +/// Error converting from `U256` to `Amount`. +/// This can fail since `Amount` is a `u128`. +#[derive(Error, Debug)] +#[error("Failed to convert U256 to Amount. {0} has more than 128 bits")] +pub struct AmountConversionError(U256); + +impl TryFrom for Amount { + type Error = AmountConversionError; + fn try_from(value: U256) -> Result { + let value = u128::try_from(&value).map_err(|_| AmountConversionError(value))?; + Ok(Amount(value)) + } +} + /// A block height to identify blocks in a chain. #[derive( Eq, @@ -1590,6 +1604,8 @@ mod metrics { mod tests { use std::str::FromStr; + use alloy_primitives::U256; + use super::{Amount, BlobContent}; use crate::identifiers::BlobType; @@ -1649,4 +1665,12 @@ mod tests { assert_eq!(hash1, hash2, "Hashes should be equal for same content"); assert_eq!(blob1.bytes(), blob2.bytes(), "Byte content should be equal"); } + + #[test] + fn test_conversion_amount_u256() { + let value_amount = Amount::from_tokens(15656565652209004332); + let value_u256: U256 = value_amount.into(); + let value_amount_rev = Amount::try_from(value_u256).expect("Failed conversion"); + assert_eq!(value_amount, value_amount_rev); + } } diff --git a/linera-base/src/identifiers.rs b/linera-base/src/identifiers.rs index e83ac6a4de1..e2411bcdb63 100644 --- a/linera-base/src/identifiers.rs +++ b/linera-base/src/identifiers.rs @@ -79,6 +79,14 @@ impl AccountOwner { } } +#[cfg(with_revm)] +impl From
for AccountOwner { + fn from(address: Address) -> Self { + let address = address.into_array(); + AccountOwner::Address20(address) + } +} + #[cfg(with_testing)] impl From for AccountOwner { fn from(address: CryptoHash) -> Self { @@ -416,7 +424,12 @@ impl GenericApplicationId { impl From> for AccountOwner { fn from(app_id: ApplicationId) -> Self { - AccountOwner::Address32(app_id.application_description_hash) + if app_id.is_evm() { + let hash_bytes = app_id.application_description_hash.as_bytes(); + AccountOwner::Address20(hash_bytes[..20].try_into().unwrap()) + } else { + AccountOwner::Address32(app_id.application_description_hash) + } } } @@ -966,6 +979,14 @@ impl ApplicationId { } } +impl ApplicationId { + /// Returns whether the `ApplicationId` is the one of an EVM application. + pub fn is_evm(&self) -> bool { + let bytes = self.application_description_hash.as_bytes(); + bytes.0[20..] == [0; 12] + } +} + #[cfg(with_revm)] impl From
for ApplicationId { fn from(address: Address) -> ApplicationId { @@ -991,18 +1012,6 @@ impl ApplicationId { pub fn bytes32(&self) -> B256 { *self.application_description_hash.as_bytes() } - - /// Returns whether the `ApplicationId` is the one of an EVM application. - pub fn is_evm(&self) -> bool { - let bytes = self.application_description_hash.as_bytes(); - let bytes = bytes.0.as_ref(); - for byte in &bytes[20..] { - if byte != &0 { - return false; - } - } - true - } } #[derive(Serialize, Deserialize)] @@ -1267,4 +1276,18 @@ mod tests { let stream_id2 = StreamId::from_str(&format!("{stream_id1}")).unwrap(); assert_eq!(stream_id1, stream_id2); } + + #[cfg(with_revm)] + #[test] + fn test_address_account_owner() { + use alloy_primitives::Address; + let mut vec = Vec::new(); + for i in 0..20 { + vec.push(i as u8); + } + let address1 = Address::from_slice(&vec); + let account_owner = AccountOwner::from(address1); + let address2 = account_owner.to_evm_address().unwrap(); + assert_eq!(address1, address2); + } } diff --git a/linera-base/src/vm.rs b/linera-base/src/vm.rs index 391fbf84fe1..51187ad7f2d 100644 --- a/linera-base/src/vm.rs +++ b/linera-base/src/vm.rs @@ -11,6 +11,8 @@ use linera_witty::{WitLoad, WitStore, WitType}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::data_types::Amount; + #[derive( Clone, Copy, @@ -63,7 +65,46 @@ pub enum EvmQuery { /// A read-only query. Query(Vec), /// A request to schedule an operation that can mutate the application state. - Mutation(Vec), + Operation(Vec), /// A request to schedule operations that can mutate the application state. - Mutations(Vec>), + Operations(Vec>), +} + +/// An EVM operation containing a value and argument data. +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct EvmOperation { + /// The amount being transferred. + pub value: alloy_primitives::U256, + /// The encoded argument data. + pub argument: Vec, +} + +impl EvmOperation { + /// An EVM transaction with a specified amount and function input. + pub fn new(amount: Amount, argument: Vec) -> Self { + Self { + value: amount.into(), + argument, + } + } + + /// Convert the input to a `Vec` if possible. + pub fn to_bytes(&self) -> Result, bcs::Error> { + bcs::to_bytes(&self) + } + + /// Creates an `EvmQuery` from the input. + pub fn to_evm_query(&self) -> Result { + Ok(EvmQuery::Operation(self.to_bytes()?)) + } +} + +/// The instantiation argument to EVM smart contracts. +/// `value` is the amount being transferred. +#[derive(Default, Serialize, Deserialize)] +pub struct EvmInstantiation { + /// The initial value put in the instantiation of the contract. + pub value: alloy_primitives::U256, + /// The input to the `fn instantiate` of the EVM smart contract. + pub argument: Vec, } diff --git a/linera-execution/solidity/Linera.sol b/linera-execution/solidity/Linera.sol index 1165630f8ff..2da4cc62e73 100644 --- a/linera-execution/solidity/Linera.sol +++ b/linera-execution/solidity/Linera.sol @@ -27,6 +27,15 @@ library Linera { return ChainId(entry.value.value); } + function chainid_to(Linera.ChainId memory entry) + internal + pure + returns (LineraTypes.ChainId memory) + { + LineraTypes.CryptoHash memory hash = LineraTypes.CryptoHash(entry.value); + return LineraTypes.ChainId(hash); + } + struct AccountOwner { uint8 choice; // choice=0 corresponds to Reserved @@ -54,6 +63,21 @@ library Linera { return LineraTypes.AccountOwner(owner.choice, owner.reserved, hash, owner.address20); } + struct Account { + ChainId chain_id; + AccountOwner owner; + } + + function account_to(Linera.Account memory account_i) + internal + pure + returns (LineraTypes.Account memory) + { + LineraTypes.ChainId memory chain_id2 = chainid_to(account_i.chain_id); + LineraTypes.AccountOwner memory owner2 = accountowner_to(account_i.owner); + return LineraTypes.Account(chain_id2, owner2); + } + struct AccountOwnerBalance { AccountOwner account_owner; uint256 balance; @@ -551,6 +575,19 @@ library Linera { return opt_uint32_from(output2); } + function transfer(Linera.Account memory account, uint256 amount) internal { + address precompile = address(0x0b); + LineraTypes.Account memory account2 = account_to(account); + LineraTypes.Amount memory amount2 = LineraTypes.Amount(bytes32(amount)); + LineraTypes.ContractRuntimePrecompile_Transfer memory transfer_ = LineraTypes.ContractRuntimePrecompile_Transfer(account2, amount2); + LineraTypes.ContractRuntimePrecompile memory contract_ = LineraTypes.ContractRuntimePrecompile_case_transfer(transfer_); + LineraTypes.RuntimePrecompile memory input1 = LineraTypes.RuntimePrecompile_case_contract(contract_); + bytes memory input2 = LineraTypes.bcs_serialize_RuntimePrecompile(input1); + (bool success, bytes memory output) = precompile.call(input2); + require(success); + require(output.length == 0); + } + // ServiceRuntime functions. function try_query_application(bytes32 universal_address, bytes memory argument) internal returns (bytes memory) { diff --git a/linera-execution/solidity/LineraTypes.sol b/linera-execution/solidity/LineraTypes.sol index eec565b5516..9ddeb13303d 100644 --- a/linera-execution/solidity/LineraTypes.sol +++ b/linera-execution/solidity/LineraTypes.sol @@ -51,6 +51,45 @@ library LineraTypes { return (0,0); } + struct Account { + ChainId chain_id; + AccountOwner owner; + } + + function bcs_serialize_Account(Account memory input) + internal + pure + returns (bytes memory) + { + bytes memory result = bcs_serialize_ChainId(input.chain_id); + return abi.encodePacked(result, bcs_serialize_AccountOwner(input.owner)); + } + + function bcs_deserialize_offset_Account(uint256 pos, bytes memory input) + internal + pure + returns (uint256, Account memory) + { + uint256 new_pos; + ChainId memory chain_id; + (new_pos, chain_id) = bcs_deserialize_offset_ChainId(pos, input); + AccountOwner memory owner; + (new_pos, owner) = bcs_deserialize_offset_AccountOwner(new_pos, input); + return (new_pos, Account(chain_id, owner)); + } + + function bcs_deserialize_Account(bytes memory input) + internal + pure + returns (Account memory) + { + uint256 new_pos; + Account memory value; + (new_pos, value) = bcs_deserialize_offset_Account(0, input); + require(new_pos == input.length, "incomplete deserialization"); + return value; + } + struct AccountOwner { uint8 choice; // choice=0 corresponds to Reserved @@ -584,6 +623,8 @@ library LineraTypes { // choice=10 corresponds to QueryService ContractRuntimePrecompile_QueryService query_service; // choice=11 corresponds to ValidationRound + // choice=12 corresponds to Transfer + ContractRuntimePrecompile_Transfer transfer_; } function ContractRuntimePrecompile_case_authenticated_owner() @@ -598,7 +639,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(0), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(0), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_message_origin_chain_id() @@ -613,7 +655,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(1), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(1), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_message_is_bouncing() @@ -628,7 +671,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(2), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(2), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_authenticated_caller_id() @@ -643,7 +687,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(3), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(3), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_send_message(ContractRuntimePrecompile_SendMessage memory send_message) @@ -657,7 +702,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(4), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(4), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_try_call_application(ContractRuntimePrecompile_TryCallApplication memory try_call_application) @@ -671,7 +717,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(5), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(5), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_emit(ContractRuntimePrecompile_Emit memory emit_) @@ -685,7 +732,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(6), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(6), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_read_event(ContractRuntimePrecompile_ReadEvent memory read_event) @@ -699,7 +747,8 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(7), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(7), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_subscribe_to_events(ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events) @@ -713,7 +762,8 @@ library LineraTypes { ContractRuntimePrecompile_ReadEvent memory read_event; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(8), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(8), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_unsubscribe_from_events(ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events) @@ -727,7 +777,8 @@ library LineraTypes { ContractRuntimePrecompile_ReadEvent memory read_event; ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(9), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(9), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_query_service(ContractRuntimePrecompile_QueryService memory query_service) @@ -741,7 +792,8 @@ library LineraTypes { ContractRuntimePrecompile_ReadEvent memory read_event; ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; - return ContractRuntimePrecompile(uint8(10), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(10), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function ContractRuntimePrecompile_case_validation_round() @@ -756,7 +808,23 @@ library LineraTypes { ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; ContractRuntimePrecompile_QueryService memory query_service; - return ContractRuntimePrecompile(uint8(11), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service); + ContractRuntimePrecompile_Transfer memory transfer_; + return ContractRuntimePrecompile(uint8(11), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); + } + + function ContractRuntimePrecompile_case_transfer(ContractRuntimePrecompile_Transfer memory transfer_) + internal + pure + returns (ContractRuntimePrecompile memory) + { + ContractRuntimePrecompile_SendMessage memory send_message; + ContractRuntimePrecompile_TryCallApplication memory try_call_application; + ContractRuntimePrecompile_Emit memory emit_; + ContractRuntimePrecompile_ReadEvent memory read_event; + ContractRuntimePrecompile_SubscribeToEvents memory subscribe_to_events; + ContractRuntimePrecompile_UnsubscribeFromEvents memory unsubscribe_from_events; + ContractRuntimePrecompile_QueryService memory query_service; + return ContractRuntimePrecompile(uint8(12), send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_); } function bcs_serialize_ContractRuntimePrecompile(ContractRuntimePrecompile memory input) @@ -785,6 +853,9 @@ library LineraTypes { if (input.choice == 10) { return abi.encodePacked(input.choice, bcs_serialize_ContractRuntimePrecompile_QueryService(input.query_service)); } + if (input.choice == 12) { + return abi.encodePacked(input.choice, bcs_serialize_ContractRuntimePrecompile_Transfer(input.transfer_)); + } return abi.encodePacked(input.choice); } @@ -824,8 +895,12 @@ library LineraTypes { if (choice == 10) { (new_pos, query_service) = bcs_deserialize_offset_ContractRuntimePrecompile_QueryService(new_pos, input); } - require(choice < 12); - return (new_pos, ContractRuntimePrecompile(choice, send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service)); + ContractRuntimePrecompile_Transfer memory transfer_; + if (choice == 12) { + (new_pos, transfer_) = bcs_deserialize_offset_ContractRuntimePrecompile_Transfer(new_pos, input); + } + require(choice < 13); + return (new_pos, ContractRuntimePrecompile(choice, send_message, try_call_application, emit_, read_event, subscribe_to_events, unsubscribe_from_events, query_service, transfer_)); } function bcs_deserialize_ContractRuntimePrecompile(bytes memory input) @@ -1043,6 +1118,45 @@ library LineraTypes { return value; } + struct ContractRuntimePrecompile_Transfer { + Account account; + Amount amount; + } + + function bcs_serialize_ContractRuntimePrecompile_Transfer(ContractRuntimePrecompile_Transfer memory input) + internal + pure + returns (bytes memory) + { + bytes memory result = bcs_serialize_Account(input.account); + return abi.encodePacked(result, bcs_serialize_Amount(input.amount)); + } + + function bcs_deserialize_offset_ContractRuntimePrecompile_Transfer(uint256 pos, bytes memory input) + internal + pure + returns (uint256, ContractRuntimePrecompile_Transfer memory) + { + uint256 new_pos; + Account memory account; + (new_pos, account) = bcs_deserialize_offset_Account(pos, input); + Amount memory amount; + (new_pos, amount) = bcs_deserialize_offset_Amount(new_pos, input); + return (new_pos, ContractRuntimePrecompile_Transfer(account, amount)); + } + + function bcs_deserialize_ContractRuntimePrecompile_Transfer(bytes memory input) + internal + pure + returns (ContractRuntimePrecompile_Transfer memory) + { + uint256 new_pos; + ContractRuntimePrecompile_Transfer memory value; + (new_pos, value) = bcs_deserialize_offset_ContractRuntimePrecompile_Transfer(0, input); + require(new_pos == input.length, "incomplete deserialization"); + return value; + } + struct ContractRuntimePrecompile_TryCallApplication { ApplicationId target; bytes argument; diff --git a/linera-execution/solidity/LineraTypes.yaml b/linera-execution/solidity/LineraTypes.yaml index 669034d0595..ee67d3005cf 100644 --- a/linera-execution/solidity/LineraTypes.yaml +++ b/linera-execution/solidity/LineraTypes.yaml @@ -48,6 +48,12 @@ Amount: TUPLEARRAY: CONTENT: U8 SIZE: 32 +Account: + STRUCT: + - chain_id: + TYPENAME: ChainId + - owner: + TYPENAME: AccountOwner AccountOwnerBalanceInner: STRUCT: - account_owner: @@ -196,6 +202,13 @@ ContractRuntimePrecompile: - query: BYTES 11: ValidationRound: UNIT + 12: + Transfer: + STRUCT: + - account: + TYPENAME: Account + - amount: + TYPENAME: Amount ServiceRuntimePrecompile: ENUM: 0: diff --git a/linera-execution/src/evm/database.rs b/linera-execution/src/evm/database.rs index e8443ca8910..05dde0aabea 100644 --- a/linera-execution/src/evm/database.rs +++ b/linera-execution/src/evm/database.rs @@ -9,7 +9,7 @@ use std::{ sync::{Arc, Mutex}, }; -use linera_base::vm::VmRuntime; +use linera_base::{data_types::Amount, ensure, identifiers::Account, vm::VmRuntime}; use linera_views::common::from_bytes_option; use revm::{primitives::keccak256, Database, DatabaseCommit, DatabaseRef}; use revm_context::BlockEnv; @@ -19,8 +19,8 @@ use revm_primitives::{address, Address, B256, U256}; use revm_state::{AccountInfo, Bytecode, EvmState}; use crate::{ - ApplicationId, BaseRuntime, Batch, ContractRuntime, EvmExecutionError, ExecutionError, - ServiceRuntime, + evm::inputs::{FAUCET_ADDRESS, FAUCET_BALANCE, ZERO_ADDRESS}, + BaseRuntime, Batch, ContractRuntime, EvmExecutionError, ExecutionError, ServiceRuntime, }; // The runtime costs are not available in service operations. @@ -77,6 +77,10 @@ pub(crate) struct DatabaseRuntime { /// This is the EVM address of the contract. /// At the creation, it is set to `Address::ZERO` and then later set to the correct value. pub contract_address: Address, + /// The caller to the smart contract. + pub caller: Address, + /// The value of the smart contract. + pub value: U256, /// The runtime of the contract. pub runtime: Arc>, /// The uncommitted changes to the contract. @@ -92,6 +96,8 @@ impl Clone for DatabaseRuntime { Self { storage_stats: self.storage_stats.clone(), contract_address: self.contract_address, + caller: self.caller, + value: self.value, runtime: self.runtime.clone(), changes: self.changes.clone(), is_revm_instantiated: self.is_revm_instantiated, @@ -107,19 +113,13 @@ pub enum KeyCategory { Storage, } -fn application_id_to_address(application_id: ApplicationId) -> Address { - let application_id: [u64; 4] = <[u64; 4]>::from(application_id.application_description_hash); - let application_id: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(application_id); - Address::from_slice(&application_id[0..20]) -} - impl DatabaseRuntime { - /// Encode the `index` of the EVM storage associated to the smart contract - /// in a linera key. - fn get_linera_key(key_prefix: &[u8], index: U256) -> Result, ExecutionError> { + /// Encodes the `index` of the EVM storage associated to the smart contract + /// in a Linera key. + fn get_linera_key(key_prefix: &[u8], index: U256) -> Vec { let mut key = key_prefix.to_vec(); - bcs::serialize_into(&mut key, &index)?; - Ok(key) + key.extend(index.as_le_slice()); + key } /// Returns the tag associated to the contract. @@ -138,6 +138,8 @@ impl DatabaseRuntime { Self { storage_stats: Arc::new(Mutex::new(storage_stats)), contract_address: Address::ZERO, + caller: Address::ZERO, + value: U256::ZERO, runtime: Arc::new(Mutex::new(runtime)), changes: HashMap::new(), is_revm_instantiated: false, @@ -209,17 +211,54 @@ where { type Error = ExecutionError; + /// The `basic_ref` is the function for reading the state of the application. fn basic_ref(&self, address: Address) -> Result, ExecutionError> { + if address == FAUCET_ADDRESS { + return Ok(Some(AccountInfo { + balance: FAUCET_BALANCE, + ..AccountInfo::default() + })); + } if !self.changes.is_empty() { - let account = self.changes.get(&address).unwrap(); - return Ok(Some(account.info.clone())); + // This is the case of service calls with empty storage. + let account = self.changes.get(&address); + return Ok(account.map(|account| account.info.clone())); } let mut runtime = self.runtime.lock().unwrap(); + let account_owner = address.into(); + // The balances being used are the ones of Linera. So, we need to + // access them at first. + let balance = runtime.read_owner_balance(account_owner)?; + + let balance: U256 = balance.into(); let key_info = Self::get_address_key(KeyCategory::AccountInfo as u8, address); let promise = runtime.read_value_bytes_new(key_info)?; let result = runtime.read_value_bytes_wait(&promise)?; - let account_info = from_bytes_option::(&result)?; - Ok(account_info) + let mut account_info = match result { + None => AccountInfo::default(), + Some(bytes) => bcs::from_bytes(&bytes)?, + }; + // The funds have been immediately deposited in deposit_funds. + // The EVM will do the same before even the execution of + // the constructor or function. + // Therefore, we need to adjust the values. + // This will ensure that at any time the balances in EVM + // and Linera are exactly matching during the execution. + let start_balance = if self.caller == address { + balance + self.value + } else if self.contract_address == address { + assert!( + balance >= self.value, + "We should have balance >= self.value" + ); + balance - self.value + } else { + balance + }; + account_info.balance = start_balance; + // We return an account as there is no difference between + // a default account and the absence of account. + Ok(Some(account_info)) } fn code_by_hash_ref(&self, _code_hash: B256) -> Result { @@ -235,7 +274,7 @@ where }); } let key_prefix = Self::get_address_key(KeyCategory::Storage as u8, address); - let key = Self::get_linera_key(&key_prefix, index)?; + let key = Self::get_linera_key(&key_prefix, index); { let mut storage_stats = self.storage_stats.lock().unwrap(); storage_stats.key_read += 1; @@ -263,6 +302,17 @@ where let mut runtime = self.runtime.lock().unwrap(); let mut batch = Batch::new(); for (address, account) in &self.changes { + if address == &FAUCET_ADDRESS { + // We do not write the faucet address nor expect any coherency from it. + continue; + } + let owner = (*address).into(); + let linera_balance: U256 = runtime.read_owner_balance(owner)?.into(); + let revm_balance = account.info.balance; + ensure!( + linera_balance == revm_balance, + EvmExecutionError::IncoherentBalances(*address, linera_balance, revm_balance) + ); if !account.is_touched() { continue; } @@ -295,7 +345,7 @@ where if value.present_value() == value.original_value() { storage_stats.key_no_operation += 1; } else { - let key = Self::get_linera_key(&key_prefix, *index)?; + let key = Self::get_linera_key(&key_prefix, *index); if value.original_value() == U256::ZERO { batch.put_key_value(key, &value.present_value())?; storage_stats.key_set += 1; @@ -347,7 +397,7 @@ where pub fn set_contract_address(&mut self) -> Result<(), ExecutionError> { let mut runtime = self.runtime.lock().unwrap(); let application_id = runtime.application_id()?; - self.contract_address = application_id_to_address(application_id); + self.contract_address = application_id.evm_address(); Ok(()) } @@ -385,7 +435,7 @@ where let timestamp_linera = runtime.read_system_timestamp()?; let timestamp_evm = timestamp_linera.micros() / 1_000_000; // The basefee is the minimum feee for executing. We have no such - // concept in Linera + // concept in Linera. let basefee = 0; let chain_id = runtime.chain_id()?; let entry = format!("{}{}", chain_id, block_height_linera); @@ -429,6 +479,27 @@ where block_env.gas_limit = gas_limit; Ok(block_env) } + + pub fn deposit_funds(&self) -> Result<(), ExecutionError> { + if self.value != U256::ZERO { + if self.caller == ZERO_ADDRESS { + let error = EvmExecutionError::UnknownSigner; + return Err(error.into()); + } + let source = self.caller.into(); + let amount = Amount::try_from(self.value).map_err(EvmExecutionError::from)?; + let mut runtime = self.runtime.lock().expect("The lock should be possible"); + let chain_id = runtime.chain_id()?; + let application_id = runtime.application_id()?; + let owner = application_id.into(); + let destination = Account { chain_id, owner }; + let authenticated_caller = runtime.authenticated_caller_id()?; + if authenticated_caller.is_none() { + runtime.transfer(source, destination, amount)?; + } + } + Ok(()) + } } impl DatabaseRuntime diff --git a/linera-execution/src/evm/inputs.rs b/linera-execution/src/evm/inputs.rs new file mode 100644 index 00000000000..83a47444ad3 --- /dev/null +++ b/linera-execution/src/evm/inputs.rs @@ -0,0 +1,266 @@ +// Copyright (c) Zefchain Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! Code specific to the input of functions, that is selectors, +//! constructor argument and instantiation argument. + +use linera_base::{ + crypto::CryptoHash, + data_types::StreamUpdate, + ensure, + identifiers::{ApplicationId, ChainId, StreamName}, +}; +use revm_primitives::{address, Address, U256}; + +use crate::EvmExecutionError; + +alloy_sol_types::sol! { + struct InternalApplicationId { + bytes32 application_description_hash; + } + + struct InternalGenericApplicationId { + uint8 choice; + InternalApplicationId user; + } + + struct InternalStreamName { + bytes stream_name; + } + + struct InternalStreamId { + InternalGenericApplicationId application_id; + InternalStreamName stream_name; + } + + struct InternalChainId { + bytes32 value; + } + + struct InternalStreamUpdate { + InternalChainId chain_id; + InternalStreamId stream_id; + uint32 previous_index; + uint32 next_index; + } + + function process_streams(InternalStreamUpdate[] internal_streams); +} + +// This is the precompile address that contains the Linera specific +// functionalities accessed from the EVM. +pub(crate) const PRECOMPILE_ADDRESS: Address = address!("000000000000000000000000000000000000000b"); + +// This is the zero address used when no address can be obtained from `authenticated_signer` +// and `authenticated_caller_id`. This scenario does not occur if an Address20 user calls or +// if an EVM contract calls another EVM contract. +pub(crate) const ZERO_ADDRESS: Address = address!("0000000000000000000000000000000000000000"); + +// This is the address being used for service calls. +pub(crate) const SERVICE_ADDRESS: Address = address!("0000000000000000000000000000000000002000"); + +/// This is the address used for getting ethers and transfering them to. +pub(crate) const FAUCET_ADDRESS: Address = address!("0000000000000000000000000000000000004000"); +pub(crate) const FAUCET_BALANCE: U256 = U256::from_limbs([ + 0xffffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + 0x7fffffffffffffff, +]); + +/// This is the selector of `execute_message` that should be called +/// only from a submitted message +pub(crate) const EXECUTE_MESSAGE_SELECTOR: &[u8] = &[173, 125, 234, 205]; + +/// This is the selector of `process_streams` that should be called +/// only from a submitted message +pub(crate) const PROCESS_STREAMS_SELECTOR: &[u8] = &[254, 72, 102, 28]; + +/// This is the selector of `instantiate` that should be called +/// only when creating a new instance of a shared contract +pub(crate) const INSTANTIATE_SELECTOR: &[u8] = &[156, 163, 60, 158]; + +pub(crate) fn forbid_execute_operation_origin(vec: &[u8]) -> Result<(), EvmExecutionError> { + ensure!( + vec != EXECUTE_MESSAGE_SELECTOR, + EvmExecutionError::IllegalOperationCall("function execute_message".to_string(),) + ); + ensure!( + vec != PROCESS_STREAMS_SELECTOR, + EvmExecutionError::IllegalOperationCall("function process_streams".to_string(),) + ); + ensure!( + vec != INSTANTIATE_SELECTOR, + EvmExecutionError::IllegalOperationCall("function instantiate".to_string(),) + ); + Ok(()) +} + +pub(crate) fn ensure_message_length( + actual_length: usize, + min_length: usize, +) -> Result<(), EvmExecutionError> { + ensure!( + actual_length >= min_length, + EvmExecutionError::OperationIsTooShort + ); + Ok(()) +} + +pub(crate) fn ensure_selector_presence( + module: &[u8], + selector: &[u8], + fct_name: &str, +) -> Result<(), EvmExecutionError> { + ensure!( + has_selector(module, selector), + EvmExecutionError::MissingFunction(fct_name.to_string()) + ); + Ok(()) +} + +pub(crate) fn has_selector(module: &[u8], selector: &[u8]) -> bool { + let push4 = 0x63; // An EVM instruction + let mut vec = vec![push4]; + vec.extend(selector); + module.windows(5).any(|window| window == vec) +} + +pub(crate) fn get_revm_instantiation_bytes(value: Vec) -> Vec { + use alloy_primitives::Bytes; + use alloy_sol_types::{sol, SolCall}; + sol! { + function instantiate(bytes value); + } + let bytes = Bytes::from(value); + let argument = instantiateCall { value: bytes }; + argument.abi_encode() +} + +pub(crate) fn get_revm_execute_message_bytes(value: Vec) -> Vec { + use alloy_primitives::Bytes; + use alloy_sol_types::{sol, SolCall}; + sol! { + function execute_message(bytes value); + } + let value = Bytes::from(value); + let argument = execute_messageCall { value }; + argument.abi_encode() +} + +pub(crate) fn get_revm_process_streams_bytes(streams: Vec) -> Vec { + use alloy_primitives::{Bytes, B256}; + use alloy_sol_types::SolCall; + use linera_base::identifiers::{GenericApplicationId, StreamId}; + + fn crypto_hash_to_internal_crypto_hash(hash: CryptoHash) -> B256 { + let hash = <[u64; 4]>::from(hash); + let hash = linera_base::crypto::u64_array_to_be_bytes(hash); + hash.into() + } + + fn chain_id_to_internal_chain_id(chain_id: ChainId) -> InternalChainId { + let value = crypto_hash_to_internal_crypto_hash(chain_id.0); + InternalChainId { value } + } + + fn application_id_to_internal_application_id( + application_id: ApplicationId, + ) -> InternalApplicationId { + let application_description_hash = + crypto_hash_to_internal_crypto_hash(application_id.application_description_hash); + InternalApplicationId { + application_description_hash, + } + } + + fn stream_name_to_internal_stream_name(stream_name: StreamName) -> InternalStreamName { + let stream_name = Bytes::from(stream_name.0); + InternalStreamName { stream_name } + } + + fn generic_application_id_to_internal_generic_application_id( + generic_application_id: GenericApplicationId, + ) -> InternalGenericApplicationId { + match generic_application_id { + GenericApplicationId::System => { + let application_description_hash = B256::ZERO; + InternalGenericApplicationId { + choice: 0, + user: InternalApplicationId { + application_description_hash, + }, + } + } + GenericApplicationId::User(application_id) => InternalGenericApplicationId { + choice: 1, + user: application_id_to_internal_application_id(application_id), + }, + } + } + + fn stream_id_to_internal_stream_id(stream_id: StreamId) -> InternalStreamId { + let application_id = + generic_application_id_to_internal_generic_application_id(stream_id.application_id); + let stream_name = stream_name_to_internal_stream_name(stream_id.stream_name); + InternalStreamId { + application_id, + stream_name, + } + } + + fn stream_update_to_internal_stream_update( + stream_update: StreamUpdate, + ) -> InternalStreamUpdate { + let chain_id = chain_id_to_internal_chain_id(stream_update.chain_id); + let stream_id = stream_id_to_internal_stream_id(stream_update.stream_id); + InternalStreamUpdate { + chain_id, + stream_id, + previous_index: stream_update.previous_index, + next_index: stream_update.next_index, + } + } + + let internal_streams = streams + .into_iter() + .map(stream_update_to_internal_stream_update) + .collect(); + + let fct_call = process_streamsCall { internal_streams }; + fct_call.abi_encode() +} + +#[cfg(test)] +mod tests { + use revm_primitives::keccak256; + + use crate::evm::inputs::{ + process_streamsCall, EXECUTE_MESSAGE_SELECTOR, INSTANTIATE_SELECTOR, + PROCESS_STREAMS_SELECTOR, + }; + + // The function keccak256 is not const so we cannot build the execute_message + // selector directly. + #[test] + fn check_execute_message_selector() { + let selector = &keccak256("execute_message(bytes)".as_bytes())[..4]; + assert_eq!(selector, EXECUTE_MESSAGE_SELECTOR); + } + + #[test] + fn check_process_streams_selector() { + use alloy_sol_types::SolCall; + assert_eq!( + process_streamsCall::SIGNATURE, + "process_streams(((bytes32),((uint8,(bytes32)),(bytes)),uint32,uint32)[])" + ); + assert_eq!(process_streamsCall::SELECTOR, PROCESS_STREAMS_SELECTOR); + } + + #[test] + fn check_instantiate_selector() { + let selector = &keccak256("instantiate(bytes)".as_bytes())[..4]; + assert_eq!(selector, INSTANTIATE_SELECTOR); + } +} diff --git a/linera-execution/src/evm/mod.rs b/linera-execution/src/evm/mod.rs index 6dbdddfb0c1..bace2dd0afd 100644 --- a/linera-execution/src/evm/mod.rs +++ b/linera-execution/src/evm/mod.rs @@ -9,14 +9,18 @@ mod data_types; mod database; +pub mod inputs; pub mod revm; +use linera_base::data_types::AmountConversionError; use revm_context::result::{HaltReason, Output, SuccessReason}; -use revm_primitives::Log; +use revm_primitives::{Address, Log, U256}; use thiserror::Error; #[derive(Debug, Error)] pub enum EvmExecutionError { + #[error(transparent)] + AmountConversionError(#[from] AmountConversionError), #[error("Failed to load contract EVM module: {_0}")] LoadContractModule(#[source] anyhow::Error), #[error("Failed to load service EVM module: {_0}")] @@ -27,6 +31,16 @@ pub enum EvmExecutionError { IllegalOperationCall(String), #[error("runtime error")] RuntimeError(String), + #[error("The balances are incoherent for address {0}, balances {1}, {2}")] + IncoherentBalances(Address, U256, U256), + #[error("Unknown signer")] + UnknownSigner, + #[error("No delegate call")] + NoDelegateCall, + #[error("No transfer in services")] + NoTransferInServices, + #[error("No transfer in Wasm application call")] + NoTransferInRuntimeCall, #[error("The function {0} is being called but is missing from the bytecode API")] MissingFunction(String), #[error("Incorrect contract creation: {0}")] diff --git a/linera-execution/src/evm/revm.rs b/linera-execution/src/evm/revm.rs index 338834f62b9..a395e11c505 100644 --- a/linera-execution/src/evm/revm.rs +++ b/linera-execution/src/evm/revm.rs @@ -4,31 +4,37 @@ //! Code specific to the usage of the [Revm](https://bluealloy.github.io/revm/) runtime. use core::ops::Range; -use std::{collections::BTreeSet, convert::TryFrom}; +use std::{ + collections::BTreeSet, + convert::TryFrom, + sync::{Arc, Mutex}, +}; #[cfg(with_metrics)] use linera_base::prometheus_util::MeasureLatency as _; use linera_base::{ crypto::CryptoHash, - data_types::{Bytecode, Resources, SendMessageRequest, StreamUpdate}, + data_types::{ + Amount, ApplicationDescription, Bytecode, Resources, SendMessageRequest, StreamUpdate, + }, ensure, - identifiers::{AccountOwner, ApplicationId, ChainId, StreamName}, - vm::{EvmQuery, VmRuntime}, + identifiers::{Account, AccountOwner, ApplicationId, ChainId, ModuleId, StreamName}, + vm::{EvmInstantiation, EvmOperation, EvmQuery, VmRuntime}, }; use revm::{primitives::Bytes, InspectCommitEvm, InspectEvm, Inspector}; use revm_context::{ result::{ExecutionResult, Output, SuccessReason}, - BlockEnv, Cfg, ContextTr, Evm, Journal, LocalContextTr, TxEnv, + BlockEnv, Cfg, ContextTr, Evm, Journal, JournalTr, LocalContextTr as _, TxEnv, }; use revm_database::WrapDatabaseRef; use revm_handler::{ instructions::EthInstructions, EthPrecompiles, MainnetContext, PrecompileProvider, }; use revm_interpreter::{ - CallInput, CallInputs, CallOutcome, CreateInputs, CreateOutcome, CreateScheme, Gas, InputsImpl, - InstructionResult, InterpreterResult, + CallInput, CallInputs, CallOutcome, CallValue, CreateInputs, CreateOutcome, CreateScheme, Gas, + InputsImpl, InstructionResult, InterpreterResult, }; -use revm_primitives::{address, hardfork::SpecId, Address, Log, TxKind, U256}; +use revm_primitives::{hardfork::SpecId, Address, Log, TxKind, U256}; use revm_state::EvmState; use serde::{Deserialize, Serialize}; @@ -36,24 +42,19 @@ use crate::{ evm::{ data_types::AmountU256, database::{DatabaseRuntime, StorageStats, EVM_SERVICE_GAS_LIMIT}, + inputs::{ + ensure_message_length, ensure_selector_presence, forbid_execute_operation_origin, + get_revm_execute_message_bytes, get_revm_instantiation_bytes, + get_revm_process_streams_bytes, has_selector, EXECUTE_MESSAGE_SELECTOR, FAUCET_ADDRESS, + INSTANTIATE_SELECTOR, PRECOMPILE_ADDRESS, PROCESS_STREAMS_SELECTOR, SERVICE_ADDRESS, + ZERO_ADDRESS, + }, }, BaseRuntime, ContractRuntime, ContractSyncRuntimeHandle, DataBlobHash, EvmExecutionError, EvmRuntime, ExecutionError, ServiceRuntime, ServiceSyncRuntimeHandle, UserContract, UserContractInstance, UserContractModule, UserService, UserServiceInstance, UserServiceModule, }; -/// This is the selector of the `execute_message` that should be called -/// only from a submitted message -const EXECUTE_MESSAGE_SELECTOR: &[u8] = &[173, 125, 234, 205]; - -/// This is the selector of the `process_streams` that should be called -/// only from a submitted message -const PROCESS_STREAMS_SELECTOR: &[u8] = &[254, 72, 102, 28]; - -/// This is the selector of the `instantiate` that should be called -/// only when creating a new instance of a shared contract -const INSTANTIATE_SELECTOR: &[u8] = &[156, 163, 60, 158]; - /// The selector when calling for `InterpreterResult`. This is a fictional /// selector that does not correspond to a real function. const INTERPRETER_RESULT_SELECTOR: &[u8] = &[1, 2, 3, 4]; @@ -65,116 +66,6 @@ const GET_DEPLOYED_BYTECODE_SELECTOR: &[u8] = &[21, 34, 55, 89]; /// The json serialization of a trivial vector. const JSON_EMPTY_VECTOR: &[u8] = &[91, 93]; -fn forbid_execute_operation_origin(vec: &[u8]) -> Result<(), EvmExecutionError> { - if vec == EXECUTE_MESSAGE_SELECTOR { - return Err(EvmExecutionError::IllegalOperationCall( - "function execute_message".to_string(), - )); - } - if vec == PROCESS_STREAMS_SELECTOR { - return Err(EvmExecutionError::IllegalOperationCall( - "function process_streams".to_string(), - )); - } - if vec == INSTANTIATE_SELECTOR { - return Err(EvmExecutionError::IllegalOperationCall( - "function instantiate".to_string(), - )); - } - Ok(()) -} - -fn ensure_message_length(actual_length: usize, min_length: usize) -> Result<(), EvmExecutionError> { - ensure!( - actual_length >= min_length, - EvmExecutionError::OperationIsTooShort - ); - Ok(()) -} - -fn ensure_selector_presence( - module: &[u8], - selector: &[u8], - fct_name: &str, -) -> Result<(), EvmExecutionError> { - if !has_selector(module, selector) { - return Err(EvmExecutionError::MissingFunction(fct_name.to_string())); - } - Ok(()) -} - -#[cfg(test)] -mod tests { - use revm_primitives::keccak256; - - use crate::evm::revm::{ - EXECUTE_MESSAGE_SELECTOR, INSTANTIATE_SELECTOR, PROCESS_STREAMS_SELECTOR, - }; - - // The function keccak256 is not const so we cannot build the execute_message - // selector directly. - #[test] - fn check_execute_message_selector() { - let selector = &keccak256("execute_message(bytes)".as_bytes())[..4]; - assert_eq!(selector, EXECUTE_MESSAGE_SELECTOR); - } - - #[test] - fn check_process_streams_selector() { - use alloy_sol_types::{sol, SolCall}; - sol! { - struct InternalApplicationId { - bytes32 application_description_hash; - } - - struct InternalGenericApplicationId { - uint8 choice; - InternalApplicationId user; - } - - struct InternalStreamName { - bytes stream_name; - } - - struct InternalStreamId { - InternalGenericApplicationId application_id; - InternalStreamName stream_name; - } - - struct InternalChainId { - bytes32 value; - } - - struct InternalStreamUpdate { - InternalChainId chain_id; - InternalStreamId stream_id; - uint32 previous_index; - uint32 next_index; - } - - function process_streams(InternalStreamUpdate[] internal_streams); - } - assert_eq!( - process_streamsCall::SIGNATURE, - "process_streams(((bytes32),((uint8,(bytes32)),(bytes)),uint32,uint32)[])" - ); - assert_eq!(process_streamsCall::SELECTOR, PROCESS_STREAMS_SELECTOR); - } - - #[test] - fn check_instantiate_selector() { - let selector = &keccak256("instantiate(bytes)".as_bytes())[..4]; - assert_eq!(selector, INSTANTIATE_SELECTOR); - } -} - -fn has_selector(module: &[u8], selector: &[u8]) -> bool { - let push4 = 0x63; // An EVM instruction - let mut vec = vec![push4]; - vec.extend(selector); - module.windows(5).any(|window| window == vec) -} - #[cfg(with_metrics)] mod metrics { use std::sync::LazyLock; @@ -201,144 +92,6 @@ mod metrics { }); } -fn get_revm_instantiation_bytes(value: Vec) -> Vec { - use alloy_primitives::Bytes; - use alloy_sol_types::{sol, SolCall}; - sol! { - function instantiate(bytes value); - } - let bytes = Bytes::from(value); - let argument = instantiateCall { value: bytes }; - argument.abi_encode() -} - -fn get_revm_execute_message_bytes(value: Vec) -> Vec { - use alloy_primitives::Bytes; - use alloy_sol_types::{sol, SolCall}; - sol! { - function execute_message(bytes value); - } - let value = Bytes::from(value); - let argument = execute_messageCall { value }; - argument.abi_encode() -} - -fn get_revm_process_streams_bytes(streams: Vec) -> Vec { - // See TODO(#3966) for a better support of the input. - use alloy_primitives::{Bytes, B256}; - use alloy_sol_types::{sol, SolCall}; - use linera_base::identifiers::{GenericApplicationId, StreamId}; - sol! { - struct InternalApplicationId { - bytes32 application_description_hash; - } - - struct InternalGenericApplicationId { - uint8 choice; - InternalApplicationId user; - } - - struct InternalStreamName { - bytes stream_name; - } - - struct InternalStreamId { - InternalGenericApplicationId application_id; - InternalStreamName stream_name; - } - - struct InternalChainId { - bytes32 value; - } - - struct InternalStreamUpdate { - InternalChainId chain_id; - InternalStreamId stream_id; - uint32 previous_index; - uint32 next_index; - } - - function process_streams(InternalStreamUpdate[] internal_streams); - } - - fn crypto_hash_to_internal_crypto_hash(hash: CryptoHash) -> B256 { - let hash: [u64; 4] = <[u64; 4]>::from(hash); - let hash: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(hash); - hash.into() - } - - fn chain_id_to_internal_chain_id(chain_id: ChainId) -> InternalChainId { - let value = crypto_hash_to_internal_crypto_hash(chain_id.0); - InternalChainId { value } - } - - fn application_id_to_internal_application_id( - application_id: ApplicationId, - ) -> InternalApplicationId { - let application_description_hash = - crypto_hash_to_internal_crypto_hash(application_id.application_description_hash); - InternalApplicationId { - application_description_hash, - } - } - - fn stream_name_to_internal_stream_name(stream_name: StreamName) -> InternalStreamName { - let stream_name = Bytes::from(stream_name.0); - InternalStreamName { stream_name } - } - - fn generic_application_id_to_internal_generic_application_id( - generic_application_id: GenericApplicationId, - ) -> InternalGenericApplicationId { - match generic_application_id { - GenericApplicationId::System => { - let application_description_hash = B256::ZERO; - InternalGenericApplicationId { - choice: 0, - user: InternalApplicationId { - application_description_hash, - }, - } - } - GenericApplicationId::User(application_id) => InternalGenericApplicationId { - choice: 1, - user: application_id_to_internal_application_id(application_id), - }, - } - } - - fn stream_id_to_internal_stream_id(stream_id: StreamId) -> InternalStreamId { - let application_id = - generic_application_id_to_internal_generic_application_id(stream_id.application_id); - let stream_name = stream_name_to_internal_stream_name(stream_id.stream_name); - InternalStreamId { - application_id, - stream_name, - } - } - - fn stream_update_to_internal_stream_update( - stream_update: StreamUpdate, - ) -> InternalStreamUpdate { - let chain_id = chain_id_to_internal_chain_id(stream_update.chain_id); - let stream_id = stream_id_to_internal_stream_id(stream_update.stream_id); - InternalStreamUpdate { - chain_id, - stream_id, - previous_index: stream_update.previous_index, - next_index: stream_update.next_index, - } - } - - let internal_streams = streams - .into_iter() - .map(stream_update_to_internal_stream_update) - .collect::>(); - - let fct_call = process_streamsCall { internal_streams }; - fct_call.abi_encode() -} - #[derive(Clone)] pub enum EvmContractModule { #[cfg(with_revm)] @@ -455,18 +208,6 @@ impl UserServiceModule for EvmServiceModule { type Ctx<'a, Runtime> = MainnetContext>>; -// This is the precompile address that contains the Linera specific -// functionalities accessed from the EVM. -const PRECOMPILE_ADDRESS: Address = address!("000000000000000000000000000000000000000b"); - -// This is the zero address used when no address can be obtained from `authenticated_owner` -// and `authenticated_caller_id`. This scenario does not occur if an Address20 user calls or -// if an EVM contract calls another EVM contract. -const ZERO_ADDRESS: Address = address!("0000000000000000000000000000000000000000"); - -// This is the address being used for service calls. -const SERVICE_ADDRESS: Address = address!("0000000000000000000000000000000000002000"); - fn address_to_user_application_id(address: Address) -> ApplicationId { let mut vec = vec![0_u8; 32]; vec[..20].copy_from_slice(address.as_ref()); @@ -551,6 +292,8 @@ enum ContractRuntimePrecompile { }, /// Calling `validation_round` of `ContractRuntime` ValidationRound, + /// Calling `transfer` of `ContractRuntime` + Transfer { account: Account, amount: Amount }, } /// Some functionalities from the ServiceRuntime not in BaseRuntime @@ -573,7 +316,7 @@ enum RuntimePrecompile { fn get_precompile_output(output: Vec, gas_limit: u64) -> InterpreterResult { // The gas usage is set to `gas_limit` and no spending is being done on it. - // This means that for REVM, it looks like the precompile call costs nothing. + // This means that for Revm, it looks like the precompile call costs nothing. // This is because the costs of the EVM precompile calls is accounted for // separately in Linera. let output = Bytes::from(output); @@ -586,10 +329,57 @@ fn get_precompile_output(output: Vec, gas_limit: u64) -> InterpreterResult { } } -fn get_precompile_argument(context: &mut Ctx, input: &CallInput) -> Vec { - let mut argument = Vec::new(); - get_argument(context, &mut argument, input); - argument +fn get_argument(context: &mut Ctx, input: &CallInput) -> Vec { + match input { + CallInput::Bytes(bytes) => bytes.to_vec(), + CallInput::SharedBuffer(range) => { + match context.local().shared_memory_buffer_slice(range.clone()) { + None => Vec::new(), + Some(slice) => slice.to_vec(), + } + } + } +} + +fn get_value(call_value: &CallValue) -> Result { + match call_value { + CallValue::Transfer(value) => Ok(*value), + CallValue::Apparent(_) => Err(EvmExecutionError::NoDelegateCall), + } +} + +fn get_precompile_argument( + context: &mut Ctx, + inputs: &InputsImpl, +) -> Result, ExecutionError> { + Ok(get_argument(context, &inputs.input)) +} + +fn get_call_service_argument( + context: &mut Ctx, + inputs: &CallInputs, +) -> Result, ExecutionError> { + ensure!( + get_value(&inputs.value)? == U256::ZERO, + EvmExecutionError::NoTransferInServices + ); + let mut argument = INTERPRETER_RESULT_SELECTOR.to_vec(); + argument.extend(&get_argument(context, &inputs.input)); + Ok(argument) +} + +fn get_call_contract_argument( + context: &mut Ctx, + inputs: &CallInputs, +) -> Result<(Vec, usize), ExecutionError> { + let mut final_argument = INTERPRETER_RESULT_SELECTOR.to_vec(); + let value = get_value(&inputs.value)?; + let argument = get_argument(context, &inputs.input); + let n_input = argument.len(); + let evm_operation = EvmOperation { value, argument }; + let argument = bcs::to_bytes(&evm_operation)?; + final_argument.extend(&argument); + Ok((final_argument, n_input)) } fn base_runtime_call( @@ -678,8 +468,7 @@ impl<'a, Runtime: ContractRuntime> PrecompileProvider> for Cont gas_limit: u64, ) -> Result, String> { if address == &PRECOMPILE_ADDRESS { - let input = get_precompile_argument(context, &inputs.input); - let output = Self::call_or_fail(&input, context) + let output = Self::call_or_fail(inputs, context) .map_err(|error| format!("ContractPrecompile error: {error}"))?; return Ok(Some(get_precompile_output(output, gas_limit))); } @@ -698,28 +487,60 @@ impl<'a, Runtime: ContractRuntime> PrecompileProvider> for Cont } } +fn get_evm_destination( + context: &mut Ctx<'_, Runtime>, + account: Account, +) -> Result, ExecutionError> { + let mut runtime = context.db().0.runtime.lock().unwrap(); + if runtime.chain_id()? != account.chain_id { + return Ok(None); + } + Ok(account.owner.to_evm_address()) +} + +/// If we are using the `None` case of `fn call` and `fn create` then the transfer +/// of ethers is done by Revm. On the other hand, if we are managing the call/create +/// by hand, then we need to do the transfers ourselves. +fn revm_transfer( + context: &mut Ctx<'_, Runtime>, + source: Address, + destination: Address, + value: U256, +) -> Result<(), ExecutionError> { + // In Ethereum, all transfers matter + if let Some(error) = context.journal().transfer(source, destination, value)? { + let error = format!("{error:?}"); + let error = EvmExecutionError::TransactError(error); + return Err(error.into()); + } + Ok(()) +} + impl<'a> ContractPrecompile { fn contract_runtime_call( request: ContractRuntimePrecompile, context: &mut Ctx<'a, Runtime>, ) -> Result, ExecutionError> { - let mut runtime = context.db().0.runtime.lock().unwrap(); match request { ContractRuntimePrecompile::AuthenticatedOwner => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let account_owner = runtime.authenticated_owner()?; Ok(bcs::to_bytes(&account_owner)?) } ContractRuntimePrecompile::MessageOriginChainId => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let origin_chain_id = runtime.message_origin_chain_id()?; Ok(bcs::to_bytes(&origin_chain_id)?) } ContractRuntimePrecompile::MessageIsBouncing => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let result = runtime.message_is_bouncing()?; Ok(bcs::to_bytes(&result)?) } ContractRuntimePrecompile::AuthenticatedCallerId => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let application_id = runtime.authenticated_caller_id()?; Ok(bcs::to_bytes(&application_id)?) } @@ -737,14 +558,17 @@ impl<'a> ContractPrecompile { grant, message, }; + let mut runtime = context.db().0.runtime.lock().unwrap(); runtime.send_message(send_message_request)?; Ok(vec![]) } ContractRuntimePrecompile::TryCallApplication { target, argument } => { let authenticated = true; + let mut runtime = context.db().0.runtime.lock().unwrap(); runtime.try_call_application(authenticated, target, argument) } ContractRuntimePrecompile::Emit { stream_name, value } => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let result = runtime.emit(stream_name, value)?; Ok(bcs::to_bytes(&result)?) } @@ -752,12 +576,16 @@ impl<'a> ContractPrecompile { chain_id, stream_name, index, - } => runtime.read_event(chain_id, stream_name, index), + } => { + let mut runtime = context.db().0.runtime.lock().unwrap(); + runtime.read_event(chain_id, stream_name, index) + } ContractRuntimePrecompile::SubscribeToEvents { chain_id, application_id, stream_name, } => { + let mut runtime = context.db().0.runtime.lock().unwrap(); runtime.subscribe_to_events(chain_id, application_id, stream_name)?; Ok(vec![]) } @@ -766,25 +594,49 @@ impl<'a> ContractPrecompile { application_id, stream_name, } => { + let mut runtime = context.db().0.runtime.lock().unwrap(); runtime.unsubscribe_from_events(chain_id, application_id, stream_name)?; Ok(vec![]) } ContractRuntimePrecompile::QueryService { application_id, query, - } => runtime.query_service(application_id, query), + } => { + let mut runtime = context.db().0.runtime.lock().unwrap(); + runtime.query_service(application_id, query) + } ContractRuntimePrecompile::ValidationRound => { + let mut runtime = context.db().0.runtime.lock().unwrap(); let value = runtime.validation_round()?; Ok(bcs::to_bytes(&value)?) } + ContractRuntimePrecompile::Transfer { account, amount } => { + if amount != Amount::ZERO { + let destination = { + let destination = get_evm_destination(context, account)?; + destination.unwrap_or(FAUCET_ADDRESS) + }; + let application_id = { + let mut runtime = context.db().0.runtime.lock().unwrap(); + let application_id = runtime.application_id()?; + let source = application_id.into(); + runtime.transfer(source, account, amount)?; + application_id + }; + let source: Address = application_id.evm_address(); + revm_transfer(context, source, destination, amount.into())?; + } + Ok(vec![]) + } } } fn call_or_fail( - input: &[u8], + inputs: &InputsImpl, context: &mut Ctx<'a, Runtime>, ) -> Result, ExecutionError> { - match bcs::from_bytes(input)? { + let input = get_precompile_argument(context, inputs)?; + match bcs::from_bytes(&input)? { RuntimePrecompile::Base(base_tag) => base_runtime_call(base_tag, context), RuntimePrecompile::Contract(contract_tag) => { Self::contract_runtime_call(contract_tag, context) @@ -816,10 +668,11 @@ impl<'a> ServicePrecompile { } fn call_or_fail( - input: &[u8], + inputs: &InputsImpl, context: &mut Ctx<'a, Runtime>, ) -> Result, ExecutionError> { - match bcs::from_bytes(input)? { + let input = get_precompile_argument(context, inputs)?; + match bcs::from_bytes(&input)? { RuntimePrecompile::Base(base_tag) => base_runtime_call(base_tag, context), RuntimePrecompile::Contract(_) => Err(EvmExecutionError::PrecompileError( "Contract calls are not available in GeneralServiceCall".to_string(), @@ -848,8 +701,7 @@ impl<'a, Runtime: ServiceRuntime> PrecompileProvider> for Servi gas_limit: u64, ) -> Result, String> { if address == &PRECOMPILE_ADDRESS { - let input = get_precompile_argument(context, &inputs.input); - let output = Self::call_or_fail(&input, context) + let output = Self::call_or_fail(inputs, context) .map_err(|error| format!("ServicePrecompile error: {error}"))?; return Ok(Some(get_precompile_output(output, gas_limit))); } @@ -935,6 +787,7 @@ struct CallInterceptorContract { // This is the contract address of the contract being created. contract_address: Address, precompile_addresses: BTreeSet
, + error: Arc>>, } impl Clone for CallInterceptorContract { @@ -943,29 +796,11 @@ impl Clone for CallInterceptorContract { db: self.db.clone(), contract_address: self.contract_address, precompile_addresses: self.precompile_addresses.clone(), + error: self.error.clone(), } } } -fn get_argument(context: &mut Ctx, argument: &mut Vec, input: &CallInput) { - match input { - CallInput::Bytes(bytes) => { - argument.extend(bytes.to_vec()); - } - CallInput::SharedBuffer(range) => { - if let Some(slice) = context.local().shared_memory_buffer_slice(range.clone()) { - argument.extend(&*slice); - } - } - }; -} - -fn get_call_argument(context: &mut Ctx, inputs: &CallInputs) -> Vec { - let mut argument = INTERPRETER_RESULT_SELECTOR.to_vec(); - get_argument(context, &mut argument, &inputs.input); - argument -} - impl<'a, Runtime: ContractRuntime> Inspector> for CallInterceptorContract { @@ -989,12 +824,44 @@ impl<'a, Runtime: ContractRuntime> Inspector> } impl CallInterceptorContract { + /// Gets the expected `ApplicationId`. + fn get_expected_application_id( + runtime: &mut Runtime, + module_id: ModuleId, + ) -> Result { + let chain_id = runtime.chain_id()?; + let block_height = runtime.block_height()?; + let application_index = runtime.application_index()?; + let parameters = JSON_EMPTY_VECTOR.to_vec(); // No constructor + let required_application_ids = Vec::new(); + let application_description = ApplicationDescription { + module_id, + creator_chain_id: chain_id, + block_height, + application_index, + parameters: parameters.clone(), + required_application_ids, + }; + Ok(ApplicationId::from(&application_description)) + } + + /// Publishes the `inputs`. + fn publish_create_inputs( + context: &mut Ctx<'_, Runtime>, + inputs: &mut CreateInputs, + ) -> Result { + let contract = linera_base::data_types::Bytecode::new(inputs.init_code.to_vec()); + let service = linera_base::data_types::Bytecode::new(vec![]); + let mut runtime = context.db().0.runtime.lock().unwrap(); + runtime.publish_module(contract, service, VmRuntime::Evm) + } + /// The function `fn create` of the inspector trait is called /// when a contract is going to be instantiated. Since the /// function can have some error case which are not supported /// in `fn create`, we call a `fn create_or_fail` that can /// return errors. - /// When the database runtime is created, the REVM contract + /// When the database runtime is created, the Evm contract /// may or may not have been created. Therefore, at startup /// we have `is_revm_instantiated = false`. That boolean /// can be updated after `set_is_initialized`. @@ -1041,7 +908,7 @@ impl CallInterceptorContract { /// constructor argument. /// * What needs to be determined is the deployed bytecode. /// This is stored in the AccountInfo entry. It is - /// the result of the execution by the REVM interpreter + /// the result of the execution by the Revm interpreter /// and there is no way to do it without doing the execution. /// /// The strategy for creating the contract is thus: @@ -1058,14 +925,25 @@ impl CallInterceptorContract { /// This is simply not part of create/create2 in the EVM. /// * That call to `create_application` leads to a creation of /// a new contract and so a call to `fn create_or_fail` in - /// another instance of REVM. + /// another instance of Revm. /// * When returning the `CreateOutcome`, we need to have the /// deployed bytecode. This is implemented through a special /// call to `GET_DEPLOYED_BYTECODE_SELECTOR`. This is done /// with an `execute_operation`. - /// * Data is put together as a `Some(...)` which tells REVM + /// * Data is put together as a `Some(...)` which tells Revm /// that it does not need to execute the bytecode since the /// output is given to it. + /// + /// The fact that the creation of a contract can be done in a + /// parallel way to transferring native tokens to the contract + /// being created requires us to handle this as well. + /// * We cannot do a transfer from the calling contract with + /// `deposit_funds` because we would not have the right + /// to make the transfer. + /// * Therefore, we have to do the transfer before + /// `create_application`. This forces us to know the + /// `application_id` before it is created. This is done + /// by building the `ApplicationDescription` and its hash. fn create_or_fail( &mut self, context: &mut Ctx<'_, Runtime>, @@ -1078,28 +956,57 @@ impl CallInterceptorContract { }; Ok(None) } else { - let contract = linera_base::data_types::Bytecode::new(inputs.init_code.to_vec()); - let service = linera_base::data_types::Bytecode::new(vec![]); + if inputs.value != U256::ZERO { + // decrease the balance of the contract address by the expected amount. + // We put the tokens in FAUCET_ADDRESS because we cannot transfer to + // a contract that does not yet exist. + // It is a common construction. We can see that in ERC20 contract code + // for example for burning and minting. + revm_transfer( + context, + self.db.contract_address, + FAUCET_ADDRESS, + inputs.value, + )?; + } + let module_id = Self::publish_create_inputs(context, inputs)?; let mut runtime = context.db().0.runtime.lock().unwrap(); - let module_id = runtime.publish_module(contract, service, VmRuntime::Evm)?; + let chain_id = runtime.chain_id()?; + let application_id = runtime.application_id()?; + let expected_application_id = + Self::get_expected_application_id(&mut runtime, module_id)?; + if inputs.value != U256::ZERO { + let amount = Amount::try_from(inputs.value).map_err(EvmExecutionError::from)?; + let destination = Account { + chain_id, + owner: expected_application_id.into(), + }; + let source = application_id.into(); + runtime.transfer(source, destination, amount)?; + } let parameters = JSON_EMPTY_VECTOR.to_vec(); // No constructor - let argument = JSON_EMPTY_VECTOR.to_vec(); // No call to "fn instantiate" + let evm_call = EvmInstantiation { + value: inputs.value, + argument: Vec::new(), + }; + let argument = serde_json::to_vec(&evm_call)?; let required_application_ids = Vec::new(); - let application_id = runtime.create_application( + let created_application_id = runtime.create_application( module_id, parameters, argument, required_application_ids, )?; + assert_eq!(expected_application_id, created_application_id); let argument = GET_DEPLOYED_BYTECODE_SELECTOR.to_vec(); let deployed_bytecode: Vec = - runtime.try_call_application(false, application_id, argument)?; + runtime.try_call_application(false, created_application_id, argument)?; let result = InterpreterResult { result: InstructionResult::Return, // Only possibility if no error occured. output: Bytes::from(deployed_bytecode), gas: Gas::new(inputs.gas_limit), }; - let address = application_id.evm_address(); + let address = created_application_id.evm_address(); let creation_outcome = CreateOutcome { result, address: Some(address), @@ -1108,16 +1015,31 @@ impl CallInterceptorContract { } } + /// Every call to a contract passes by this function. + /// Three kinds: + /// --- Call to the EVM smart contract itself (the first call) + /// --- Call to the PRECOMPILE smart contract. + /// --- Call to other EVM smart contract + /// + /// The first case is handled by the using Revm. This is + /// when we call this contract. + /// + /// Calling the precompile is also handled by using Revm + /// which would then use specific code for that purpose. + /// + /// Calling other Evm contracts is handled here and we have + /// to produce `Some(_)` as output. Note that in the Evm + /// transferring ethers is the same as calling a function. + /// + /// In Linera, transferring native tokens and calling a function are + /// different operations. However, the block is accepted + /// completely or not at all. Therefore, we can ensure the + /// atomicity of the operations. fn call_or_fail( &mut self, context: &mut Ctx<'_, Runtime>, inputs: &mut CallInputs, ) -> Result, ExecutionError> { - // Every call to a contract passes by this function. - // Three kinds: - // --- Call to the PRECOMPILE smart contract. - // --- Call to the EVM smart contract itself - // --- Call to other EVM smart contract if self.precompile_addresses.contains(&inputs.target_address) || inputs.target_address == self.contract_address { @@ -1125,16 +1047,58 @@ impl CallInterceptorContract { // The EVM smart contract is being called return Ok(None); } + // Handling the balances. + if let CallValue::Transfer(value) = inputs.value { + let source: AccountOwner = inputs.caller.into(); + let owner: AccountOwner = inputs.bytecode_address.into(); + if value != U256::ZERO { + // In Linera, only non-zero transfers matter + { + let mut runtime = context + .db() + .0 + .runtime + .lock() + .expect("The lock should be possible"); + let amount = Amount::try_from(value).map_err(EvmExecutionError::from)?; + let chain_id = runtime.chain_id()?; + let destination = Account { chain_id, owner }; + runtime.transfer(source, destination, amount)?; + } + revm_transfer(context, inputs.caller, inputs.target_address, value)?; + } + } // Other smart contracts calls are handled by the runtime let target = address_to_user_application_id(inputs.target_address); - let argument = get_call_argument(context, inputs); - let authenticated = true; - let result = { - let mut runtime = self.db.runtime.lock().unwrap(); - runtime.try_call_application(authenticated, target, argument)? + let (argument, n_input) = get_call_contract_argument(context, inputs)?; + let result = if n_input > 0 { + // The input is non-trivial, we assume that we are calling a contract. + // + // The correct behavior is the following: + // * If a contract exists and input is empty, then the fallback function is called + // if existing. Otherwise failure. + // * If not, then it is a user account and no execution occurs. + // + // So, the correct way is instead to test the existence of the application + // given the application_id. So, we would need following function in BaseRuntime: + // fn has_trivial_storage(&mut self, application: ApplicationId) + // -> Result; + let authenticated = true; + let result = { + let mut runtime = self.db.runtime.lock().unwrap(); + runtime.try_call_application(authenticated, target, argument)? + }; + get_interpreter_result(&result, inputs)? + } else { + // User account, no call needed. + InterpreterResult { + result: InstructionResult::Stop, + output: Bytes::default(), + gas: Gas::new(inputs.gas_limit), + } }; let call_outcome = CallOutcome { - result: get_interpreter_result(&result, inputs)?, + result, memory_offset: inputs.return_memory_offset.clone(), }; Ok(Some(call_outcome)) @@ -1186,7 +1150,7 @@ impl CallInterceptorService { /// function can have some error case which are not supported /// in `fn create`, we call a `fn create_or_fail` that can /// return errors. - /// When the database runtime is created, the REVM contract + /// When the database runtime is created, the Evm contract /// may or may not have been created. Therefore, at startup /// we have `is_revm_instantiated = false`. That boolean /// can be updated after `set_is_initialized`. @@ -1243,7 +1207,7 @@ impl CallInterceptorService { } // Other smart contracts calls are handled by the runtime let target = address_to_user_application_id(inputs.target_address); - let argument = get_call_argument(context, inputs); + let argument = get_call_service_argument(context, inputs)?; let result = { let evm_query = EvmQuery::Query(argument); let evm_query = serde_json::to_vec(&evm_query)?; @@ -1325,11 +1289,11 @@ where fn instantiate(&mut self, argument: Vec) -> Result<(), ExecutionError> { self.db.set_contract_address()?; let caller = self.get_msg_address()?; - self.initialize_contract(caller)?; + let instantiation_argument = serde_json::from_slice::(&argument)?; + self.initialize_contract(instantiation_argument.value, caller)?; if has_selector(&self.module, INSTANTIATE_SELECTOR) { - let instantiation_argument = serde_json::from_slice::>(&argument)?; - let argument = get_revm_instantiation_bytes(instantiation_argument); - let result = self.transact_commit(EvmTxKind::Call, argument, caller)?; + let argument = get_revm_instantiation_bytes(instantiation_argument.argument); + let result = self.transact_commit(EvmTxKind::Call, argument, U256::ZERO, caller)?; self.write_logs(result.logs, "instantiate")?; } Ok(()) @@ -1345,12 +1309,13 @@ where let (gas_final, output, logs) = if &operation[..4] == INTERPRETER_RESULT_SELECTOR { ensure_message_length(operation.len(), 8)?; forbid_execute_operation_origin(&operation[4..8])?; - let result = self.init_transact_commit(operation[4..].to_vec(), caller)?; + let evm_call = bcs::from_bytes::(&operation[4..])?; + let result = self.init_transact_commit(evm_call.argument, evm_call.value, caller)?; result.interpreter_result_and_logs()? } else { - ensure_message_length(operation.len(), 4)?; forbid_execute_operation_origin(&operation[..4])?; - let result = self.init_transact_commit(operation, caller)?; + let evm_call = bcs::from_bytes::(&operation)?; + let result = self.init_transact_commit(evm_call.argument, evm_call.value, caller)?; result.output_and_logs() }; self.consume_fuel(gas_final)?; @@ -1367,7 +1332,8 @@ where )?; let operation = get_revm_execute_message_bytes(message); let caller = self.get_msg_address()?; - self.execute_no_return_operation(operation, "message", caller) + let value = U256::ZERO; + self.execute_no_return_operation(operation, "message", value, caller) } fn process_streams(&mut self, streams: Vec) -> Result<(), ExecutionError> { @@ -1380,7 +1346,8 @@ where )?; // For process_streams, authenticated_owner and authenticated_called_id are None. let caller = Address::ZERO; - self.execute_no_return_operation(operation, "process_streams", caller) + let value = U256::ZERO; + self.execute_no_return_operation(operation, "process_streams", value, caller) } fn finalize(&mut self) -> Result<(), ExecutionError> { @@ -1442,9 +1409,10 @@ where &mut self, operation: Vec, origin: &str, + value: U256, caller: Address, ) -> Result<(), ExecutionError> { - let result = self.init_transact_commit(operation, caller)?; + let result = self.init_transact_commit(operation, value, caller)?; let (gas_final, output, logs) = result.output_and_logs(); self.consume_fuel(gas_final)?; self.write_logs(logs, origin)?; @@ -1456,23 +1424,24 @@ where fn init_transact_commit( &mut self, vec: Vec, + value: U256, caller: Address, ) -> Result { // An application can be instantiated in Linera sense, but not in EVM sense, // that is the contract entries corresponding to the deployed contract may // be missing. if !self.db.set_is_initialized()? { - self.initialize_contract(caller)?; + self.initialize_contract(U256::ZERO, caller)?; } - self.transact_commit(EvmTxKind::Call, vec, caller) + self.transact_commit(EvmTxKind::Call, vec, value, caller) } /// Initializes the contract. - fn initialize_contract(&mut self, caller: Address) -> Result<(), ExecutionError> { + fn initialize_contract(&mut self, value: U256, caller: Address) -> Result<(), ExecutionError> { let mut vec_init = self.module.clone(); let constructor_argument = self.db.constructor_argument()?; vec_init.extend_from_slice(&constructor_argument); - let result = self.transact_commit(EvmTxKind::Create, vec_init, caller)?; + let result = self.transact_commit(EvmTxKind::Create, vec_init, value, caller)?; result .check_contract_initialization(self.db.contract_address) .map_err(EvmExecutionError::IncorrectContractCreation)?; @@ -1512,8 +1481,12 @@ where &mut self, ch: EvmTxKind, input: Vec, + value: U256, caller: Address, ) -> Result { + self.db.caller = caller; + self.db.value = value; + self.db.deposit_funds()?; let data = Bytes::from(input); let kind = match ch { EvmTxKind::Create => TxKind::Create, @@ -1523,6 +1496,7 @@ where db: self.db.clone(), contract_address: self.db.contract_address, precompile_addresses: precompile_addresses(), + error: Arc::new(Mutex::new(None)), }; let block_env = self.db.get_contract_block_env()?; let gas_limit = { @@ -1557,9 +1531,10 @@ where nonce, gas_limit, caller, + value, ..TxEnv::default() }, - inspector, + inspector.clone(), ) .map_err(|error| { let error = format!("{:?}", error); @@ -1618,12 +1593,12 @@ where let evm_query = serde_json::from_slice(&argument)?; let query = match evm_query { EvmQuery::Query(vec) => vec, - EvmQuery::Mutation(operation) => { + EvmQuery::Operation(operation) => { let mut runtime = self.db.runtime.lock().unwrap(); runtime.schedule_operation(operation)?; return Ok(Vec::new()); } - EvmQuery::Mutations(operations) => { + EvmQuery::Operations(operations) => { let mut runtime = self.db.runtime.lock().unwrap(); for operation in operations { runtime.schedule_operation(operation)?; @@ -1682,6 +1657,10 @@ where kind: TxKind, input: Vec, ) -> Result<(ExecutionResultSuccess, EvmState), ExecutionError> { + let caller = SERVICE_ADDRESS; + let value = U256::ZERO; + self.db.caller = caller; + self.db.value = value; let data = Bytes::from(input); let block_env = self.db.get_service_block_env()?; let inspector = CallInterceptorService { @@ -1689,7 +1668,6 @@ where contract_address: self.db.contract_address, precompile_addresses: precompile_addresses(), }; - let caller = SERVICE_ADDRESS; let nonce = self.db.get_nonce(&caller)?; let result_state = { let ctx: revm_context::Context< @@ -1716,6 +1694,7 @@ where kind, data, nonce, + value, caller, gas_limit: EVM_SERVICE_GAS_LIMIT, ..TxEnv::default() diff --git a/linera-execution/src/execution_state_actor.rs b/linera-execution/src/execution_state_actor.rs index c60e48089e0..d51d27296b4 100644 --- a/linera-execution/src/execution_state_actor.rs +++ b/linera-execution/src/execution_state_actor.rs @@ -358,6 +358,11 @@ where } } + ApplicationIndex { callback } => { + let index = self.txn_tracker.application_index(); + callback.respond(index) + } + CreateApplication { chain_id, block_height, @@ -1114,6 +1119,11 @@ pub enum ExecutionRequest { callback: Sender>, }, + ApplicationIndex { + #[debug(skip)] + callback: Sender, + }, + CreateApplication { chain_id: ChainId, block_height: BlockHeight, diff --git a/linera-execution/src/lib.rs b/linera-execution/src/lib.rs index 1a6f4d05880..5a5bed66c35 100644 --- a/linera-execution/src/lib.rs +++ b/linera-execution/src/lib.rs @@ -865,6 +865,10 @@ pub trait ContractRuntime: BaseRuntime { required_application_ids: Vec, ) -> Result; + /// Returns the current application index, i.e. the number of new applications + /// created so far in this block. + fn application_index(&mut self) -> Result; + /// Creates a new data blob and returns its hash. fn create_data_blob(&mut self, bytes: Vec) -> Result; diff --git a/linera-execution/src/runtime.rs b/linera-execution/src/runtime.rs index 45a766a9566..e7cad04233b 100644 --- a/linera-execution/src/runtime.rs +++ b/linera-execution/src/runtime.rs @@ -1443,6 +1443,15 @@ impl ContractRuntime for ContractSyncRuntimeHandle { .recv_response()? } + fn application_index(&mut self) -> Result { + let index = self + .inner() + .execution_state_sender + .send_request(move |callback| ExecutionRequest::ApplicationIndex { callback })? + .recv_response()?; + Ok(index) + } + fn create_application( &mut self, module_id: ModuleId, diff --git a/linera-execution/src/transaction_tracker.rs b/linera-execution/src/transaction_tracker.rs index 92627da192d..65b711d67dc 100644 --- a/linera-execution/src/transaction_tracker.rs +++ b/linera-execution/src/transaction_tracker.rs @@ -118,6 +118,10 @@ impl TransactionTracker { self.transaction_index } + pub fn application_index(&self) -> u32 { + self.next_application_index + } + pub fn next_application_index(&mut self) -> u32 { let index = self.next_application_index; self.next_application_index += 1; diff --git a/linera-execution/tests/revm.rs b/linera-execution/tests/revm.rs index 968f82349e8..bf4b0a703af 100644 --- a/linera-execution/tests/revm.rs +++ b/linera-execution/tests/revm.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use alloy_sol_types::{sol, SolCall, SolValue}; use linera_base::{ data_types::{Amount, Blob, BlockHeight, Timestamp}, - vm::EvmQuery, + vm::{EvmInstantiation, EvmOperation, EvmQuery}, }; use linera_execution::{ evm::revm::{EvmContractModule, EvmServiceModule}, @@ -23,6 +23,11 @@ use linera_execution::{ }; use linera_views::{context::Context as _, views::View}; +fn get_operation(operation: impl alloy_sol_types::SolCall) -> Result, bcs::Error> { + let operation = EvmOperation::new(Amount::ZERO, operation.abi_encode()); + operation.to_bytes() +} + #[tokio::test] async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { let module = load_solidity_example("tests/fixtures/evm_example_counter.sol")?; @@ -40,7 +45,7 @@ async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { let args = ConstructorArgs { initial_value }; let constructor_argument = args.abi_encode(); let constructor_argument = serde_json::to_string(&constructor_argument)?.into_bytes(); - let instantiation_argument = Vec::::new(); + let instantiation_argument = EvmInstantiation::default(); let instantiation_argument = serde_json::to_string(&instantiation_argument)?.into_bytes(); let state = SystemExecutionState { description: Some(dummy_chain_description(0)), @@ -112,7 +117,7 @@ async fn test_fuel_for_counter_revm_application() -> anyhow::Result<()> { ]); value += increment; let operation = incrementCall { input: *increment }; - let bytes = operation.abi_encode(); + let bytes = get_operation(operation)?; let operation = Operation::User { application_id: app_id, bytes, @@ -159,7 +164,7 @@ async fn test_terminate_execute_operation_by_lack_of_fuel() -> anyhow::Result<() let args = ConstructorArgs { initial_value }; let constructor_argument = args.abi_encode(); let constructor_argument = serde_json::to_string(&constructor_argument)?.into_bytes(); - let instantiation_argument = Vec::::new(); + let instantiation_argument = EvmInstantiation::default(); let instantiation_argument = serde_json::to_string(&instantiation_argument)?.into_bytes(); let state = SystemExecutionState { description: Some(dummy_chain_description(0)), @@ -226,7 +231,7 @@ async fn test_terminate_execute_operation_by_lack_of_fuel() -> anyhow::Result<() ]); let input = 2; let operation = incrementCall { input }; - let bytes = operation.abi_encode(); + let bytes = get_operation(operation)?; let operation = Operation::User { application_id: app_id, bytes, @@ -254,7 +259,7 @@ async fn test_terminate_query_by_lack_of_fuel() -> anyhow::Result<()> { let args = ConstructorArgs { initial_value: 0 }; let constructor_argument = args.abi_encode(); let constructor_argument = serde_json::to_string(&constructor_argument)?.into_bytes(); - let instantiation_argument = Vec::::new(); + let instantiation_argument = EvmInstantiation::default(); let instantiation_argument = serde_json::to_string(&instantiation_argument)?.into_bytes(); let state = SystemExecutionState { description: Some(dummy_chain_description(0)), @@ -330,7 +335,7 @@ async fn test_basic_evm_features() -> anyhow::Result<()> { let constructor_argument = Vec::::new(); let constructor_argument = serde_json::to_string(&constructor_argument)?.into_bytes(); - let instantiation_argument = Vec::::new(); + let instantiation_argument = EvmInstantiation::default(); let instantiation_argument = serde_json::to_string(&instantiation_argument)?.into_bytes(); let state = SystemExecutionState { description: Some(dummy_chain_description(0)), @@ -399,7 +404,7 @@ async fn test_basic_evm_features() -> anyhow::Result<()> { // Trying a failing function, should be an error let operation = failing_functionCall {}; - let bytes = operation.abi_encode(); + let bytes = get_operation(operation)?; let operation = Operation::User { application_id: app_id, bytes, diff --git a/linera-sdk/src/linera_base_types.rs b/linera-sdk/src/linera_base_types.rs index 317c20e4eda..d957fef2af5 100644 --- a/linera-sdk/src/linera_base_types.rs +++ b/linera-sdk/src/linera_base_types.rs @@ -9,6 +9,6 @@ pub use linera_base::{ data_types::*, identifiers::*, ownership::*, - vm::{EvmQuery, VmRuntime}, + vm::{EvmInstantiation, EvmOperation, EvmQuery, VmRuntime}, BcsHexParseError, }; diff --git a/linera-service/tests/fixtures/evm_balance_and_transfer.sol b/linera-service/tests/fixtures/evm_balance_and_transfer.sol new file mode 100644 index 00000000000..d5760324d9f --- /dev/null +++ b/linera-service/tests/fixtures/evm_balance_and_transfer.sol @@ -0,0 +1,24 @@ +// Copyright (c) Zefchain Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +contract InnerContractCheck { + constructor() payable { + } + + function send_cash(address recipient, uint256 amount) external returns (uint64) { + (bool success, ) = recipient.call{value: amount}(""); + require(success, "Native token transfer failed"); + return 1; + } + + function get_balance(address account) external returns (uint256) { + uint256 balance = account.balance; + return balance; + } + + function null_operation() payable external returns (uint256) { + uint56 value = 0; + return value; + } +} diff --git a/linera-service/tests/fixtures/evm_child_subcontract.sol b/linera-service/tests/fixtures/evm_child_subcontract.sol index 2503501fcbf..d25c2b4b425 100644 --- a/linera-service/tests/fixtures/evm_child_subcontract.sol +++ b/linera-service/tests/fixtures/evm_child_subcontract.sol @@ -6,7 +6,7 @@ contract Counter { uint256 public count; - constructor(uint256 _initialValue) { + constructor(uint256 _initialValue) payable { count = _initialValue; } @@ -23,8 +23,11 @@ contract Counter { contract CounterFactory { Counter[] public counters; + constructor() payable { + } + function createCounter(uint256 initialValue) public returns (address) { - Counter newCounter = new Counter(initialValue); + Counter newCounter = new Counter{value: 1000000000000000000}(initialValue); counters.push(newCounter); return address(newCounter); } diff --git a/linera-service/tests/linera_net_tests.rs b/linera-service/tests/linera_net_tests.rs index f4546d16916..58b2c361884 100644 --- a/linera-service/tests/linera_net_tests.rs +++ b/linera-service/tests/linera_net_tests.rs @@ -21,6 +21,8 @@ use futures::{ StreamExt, }; use guard::INTEGRATION_TEST_GUARD; +#[cfg(with_revm)] +use linera_base::vm::{EvmInstantiation, EvmOperation, EvmQuery}; use linera_base::{ crypto::{CryptoHash, Secp256k1SecretKey}, data_types::Amount, @@ -435,6 +437,22 @@ impl AmmApp { } } +#[cfg(with_revm)] +fn get_zero_operation(operation: impl alloy_sol_types::SolCall) -> Result { + let operation = EvmOperation::new(Amount::ZERO, operation.abi_encode()); + operation.to_evm_query() +} + +#[cfg(with_revm)] +fn get_zero_operations( + operation: impl alloy_sol_types::SolCall, + num_operations: usize, +) -> Result { + let operation = EvmOperation::new(Amount::ZERO, operation.abi_encode()); + let operations = vec![operation.to_bytes()?; num_operations]; + Ok(EvmQuery::Operations(operations)) +} + #[cfg(with_revm)] #[cfg_attr(feature = "storage-service", test_case(LocalNetConfig::new_test(Database::Service, Network::Grpc) ; "storage_test_service_grpc"))] #[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))] @@ -443,7 +461,6 @@ impl AmmApp { #[test_log::test(tokio::test)] async fn test_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> { use alloy_sol_types::{sol, SolCall, SolValue}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -472,9 +489,9 @@ async fn test_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_counter.sol")?; - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -498,10 +515,9 @@ async fn test_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> let counter_value = read_evm_u64_entry(result); assert_eq!(counter_value, original_counter_value); - let mutation = incrementCall { input: increment }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application.run_json_query(mutation).await?; + let operation = incrementCall { input: increment }; + let operation = get_zero_operation(operation)?; + application.run_json_query(operation).await?; let result = application.run_json_query(query).await?; let counter_value = read_evm_u64_entry(result); @@ -524,7 +540,6 @@ async fn test_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> Result<()> { use alloy_primitives::U256; use alloy_sol_types::{sol, SolCall}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{ load_solidity_example_by_name, read_evm_address_entry, read_evm_u256_entry, temporary_write_evm_module, @@ -536,6 +551,7 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> let (mut net, client) = config.instantiate().await?; + let account_owner1 = client.get_owner().unwrap(); sol! { function createCounter(uint256 initialValue); function get_address(uint256 index); @@ -545,7 +561,15 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> let constructor_argument = Vec::new(); - let chain = client.load_wallet()?.default_chain().unwrap(); + let chain_id = client.load_wallet()?.default_chain().unwrap(); + let account_chain = Account::chain(chain_id); + let account1 = Account { + chain_id, + owner: account_owner1, + }; + client + .transfer_with_accounts(Amount::from_tokens(50), account_chain, account1) + .await?; let module = load_solidity_example_by_name( "tests/fixtures/evm_child_subcontract.sol", @@ -553,9 +577,13 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> )?; let (evm_contract, _dir) = temporary_write_evm_module(module)?; - let instantiation_argument = Vec::new(); + let start_value = Amount::from_tokens(27); + let instantiation_argument = EvmInstantiation { + value: start_value.into(), + argument: vec![], + }; let application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -569,21 +597,21 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> let port = get_node_port().await; let mut node_service = client.run_node_service(port, ProcessInbox::Skip).await?; - let application = node_service.make_application(&chain, &application_id)?; + let application = node_service.make_application(&chain_id, &application_id)?; // Creating the subcontracts - let mutation0 = createCounterCall { + let operation0 = createCounterCall { initialValue: U256::from(42), }; - let mutation0 = EvmQuery::Mutation(mutation0.abi_encode()); - application.run_json_query(mutation0).await?; + let operation0 = get_zero_operation(operation0)?; + application.run_json_query(operation0).await?; - let mutation1 = createCounterCall { + let operation1 = createCounterCall { initialValue: U256::from(149), }; - let mutation1 = EvmQuery::Mutation(mutation1.abi_encode()); - application.run_json_query(mutation1).await?; + let operation1 = get_zero_operation(operation1)?; + application.run_json_query(operation1).await?; let query0 = get_addressCall { index: U256::from(0), @@ -603,10 +631,10 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> // Creating the applications let application0 = ApplicationId::from(address0).with_abi::(); - let application0 = node_service.make_application(&chain, &application0)?; + let application0 = node_service.make_application(&chain_id, &application0)?; let application1 = ApplicationId::from(address1).with_abi::(); - let application1 = node_service.make_application(&chain, &application1)?; + let application1 = node_service.make_application(&chain_id, &application1)?; let query = get_valueCall {}; let query = EvmQuery::Query(query.abi_encode()); @@ -616,6 +644,18 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> let result = application1.run_json_query(query).await?; assert_eq!(read_evm_u256_entry(result), U256::from(149)); + // Created contracts have balance of 1. + let account0 = Account { + chain_id, + owner: address0.into(), + }; + let account1 = Account { + chain_id, + owner: address1.into(), + }; + assert_eq!(node_service.balance(&account0).await?, Amount::ONE); + assert_eq!(node_service.balance(&account1).await?, Amount::ONE); + node_service.ensure_is_running()?; net.ensure_is_running().await?; @@ -624,6 +664,209 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> Ok(()) } +#[cfg(with_revm)] +#[cfg_attr(feature = "storage-service", test_case(LocalNetConfig::new_test(Database::Service, Network::Grpc) ; "storage_test_service_grpc"))] +#[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))] +#[cfg_attr(feature = "dynamodb", test_case(LocalNetConfig::new_test(Database::DynamoDb, Network::Grpc) ; "aws_grpc"))] +#[cfg_attr(feature = "remote-net", test_case(RemoteNetTestingConfig::new(CloseChains) ; "remote_net_grpc"))] +#[test_log::test(tokio::test)] +async fn test_evm_end_to_end_balance_and_transfer(config: impl LineraNetConfig) -> Result<()> { + use alloy_primitives::{Address, U256}; + use alloy_sol_types::{sol, SolCall}; + use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u256_entry}; + use linera_sdk::abis::evm::EvmAbi; + + let _guard = INTEGRATION_TEST_GUARD.lock().await; + tracing::info!("Starting test {}", test_name!()); + + let (mut net, client_a) = config.instantiate().await?; + let client_b = net.make_client().await; + client_b.wallet_init(None).await?; + let client_c = net.make_client().await; + client_c.wallet_init(None).await?; + + let chain_a = client_a.load_wallet()?.default_chain().unwrap(); + let chain_b = client_a + .open_and_assign(&client_b, Amount::from_tokens(50)) + .await?; + let chain_c = client_a + .open_and_assign(&client_c, Amount::from_tokens(50)) + .await?; + let account_chain_a = Account::chain(chain_a); + + let account_owner1 = client_a.get_owner().unwrap(); + let account_owner2 = client_a.keygen().await?; + let address1 = account_owner1.to_evm_address().unwrap(); + let address2 = account_owner2.to_evm_address().unwrap(); + let account_a_1 = Account { + chain_id: chain_a, + owner: account_owner1, + }; + let account_a_2 = Account { + chain_id: chain_a, + owner: account_owner2, + }; + client_a + .transfer_with_accounts(Amount::from_tokens(50), account_chain_a, account_a_1) + .await?; + client_a + .transfer_with_accounts(Amount::from_tokens(50), account_chain_a, account_a_2) + .await?; + + sol! { + function send_cash(address recipient, uint256 amount); + function get_balance(address account); + function null_operation(); + } + + // The balance in Linera and EVM have to match. + async fn assert_contract_balance( + app: &ApplicationWrapper, + address: Address, + balance: Amount, + ) -> anyhow::Result<()> { + let query = get_balanceCall { account: address }; + let query = EvmQuery::Query(query.abi_encode()); + let result = app.run_json_query(query).await?; + let balance_256: U256 = balance.into(); + assert_eq!(read_evm_u256_entry(result), balance_256); + Ok(()) + } + + let constructor_argument = Vec::new(); + + let (evm_contract, _dir) = + get_evm_contract_path("tests/fixtures/evm_balance_and_transfer.sol")?; + + let start_value = Amount::from_tokens(4); + let instantiation_argument = EvmInstantiation { + value: start_value.into(), + argument: vec![], + }; + let application_id = client_a + .publish_and_create::, EvmInstantiation>( + evm_contract.clone(), + evm_contract, + VmRuntime::Evm, + &constructor_argument, + &instantiation_argument, + &[], + None, + ) + .await?; + + let account_owner_app: AccountOwner = application_id.into(); + let address_app = account_owner_app.to_evm_address().unwrap(); + let account_a_app = Account { + chain_id: chain_a, + owner: account_owner_app, + }; + + let port_a = get_node_port().await; + let port_b = get_node_port().await; + let port_c = get_node_port().await; + let mut node_service_a = client_a + .run_node_service(port_a, ProcessInbox::Skip) + .await?; + let mut node_service_b = client_b + .run_node_service(port_b, ProcessInbox::Skip) + .await?; + let mut node_service_c = client_c + .run_node_service(port_c, ProcessInbox::Skip) + .await?; + + let balance_a_1 = node_service_a.balance(&account_a_1).await?; + let balance_a_2 = node_service_a.balance(&account_a_2).await?; + let balance_a_app = node_service_a.balance(&account_a_app).await?; + assert_eq!(balance_a_1, Amount::from_tokens(46)); + assert_eq!(balance_a_2, Amount::from_tokens(50)); + assert_eq!(balance_a_app, Amount::from_tokens(4)); + + let app_a = node_service_a.make_application(&chain_a, &application_id)?; + let app_b = node_service_b.make_application(&chain_b, &application_id)?; + let app_c = node_service_c.make_application(&chain_c, &application_id)?; + + // Checking the balances on input + + assert_contract_balance(&app_a, address1, balance_a_1).await?; + assert_contract_balance(&app_a, address2, balance_a_2).await?; + assert_contract_balance(&app_a, address_app, balance_a_app).await?; + + // Transfering amount + + let amount = Amount::from_tokens(1); + let operation = send_cashCall { + recipient: address2, + amount: amount.into(), + }; + let operation = get_zero_operation(operation)?; + app_a.run_json_query(operation).await?; + + // Checking the balances of app_a + + let balance_a_1_after = node_service_a.balance(&account_a_1).await?; + let balance_a_2_after = node_service_a.balance(&account_a_2).await?; + let balance_a_app_after = node_service_a.balance(&account_a_app).await?; + assert_eq!(balance_a_1_after, balance_a_1); + assert_eq!(balance_a_2_after, balance_a_2 + amount); + assert_eq!(balance_a_app_after, balance_a_app - amount); + + assert_contract_balance(&app_a, address1, balance_a_1_after).await?; + assert_contract_balance(&app_a, address2, balance_a_2_after).await?; + assert_contract_balance(&app_a, address_app, balance_a_app_after).await?; + + // Doing an operation with a non-zero amount + let operation = null_operationCall {}; + let amount_operation = Amount::from(2); + let operation = EvmOperation::new(amount_operation, operation.abi_encode()); + let operation = EvmQuery::Operation(operation.to_bytes()?); + app_a.run_json_query(operation).await?; + + let balance_a_app_after2 = node_service_a.balance(&account_a_app).await?; + assert_eq!(balance_a_app_after2, balance_a_app_after + amount_operation); + + // Creating app_b via null_operation and checking balances. + let account_b_1 = Account { + chain_id: chain_b, + owner: account_owner1, + }; + let account_b_2 = Account { + chain_id: chain_b, + owner: account_owner2, + }; + let account_b_app = Account { + chain_id: chain_b, + owner: account_owner_app, + }; + + let operation = null_operationCall {}; + let operation = get_zero_operation(operation)?; + app_b.run_json_query(operation).await?; + + assert_eq!(node_service_b.balance(&account_b_1).await?, Amount::ZERO); + assert_eq!(node_service_b.balance(&account_b_2).await?, Amount::ZERO); + assert_eq!(node_service_b.balance(&account_b_app).await?, Amount::ZERO); + assert_contract_balance(&app_b, address1, Amount::ZERO).await?; + assert_contract_balance(&app_b, address2, Amount::ZERO).await?; + assert_contract_balance(&app_b, address_app, Amount::ZERO).await?; + + // Creating app_b via service calls and checking balances. + assert_contract_balance(&app_c, address1, Amount::ZERO).await?; + assert_contract_balance(&app_c, address2, Amount::ZERO).await?; + assert_contract_balance(&app_c, address_app, Amount::ZERO).await?; + + // Winding down + + node_service_a.ensure_is_running()?; + node_service_b.ensure_is_running()?; + node_service_c.ensure_is_running()?; + + net.ensure_is_running().await?; + net.terminate().await?; + + Ok(()) +} + #[cfg(with_revm)] #[cfg_attr(feature = "storage-service", test_case(LocalNetConfig::new_test(Database::Service, Network::Grpc) ; "storage_test_service_grpc"))] #[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))] @@ -632,11 +875,8 @@ async fn test_evm_end_to_end_child_subcontract(config: impl LineraNetConfig) -> #[test_log::test(tokio::test)] async fn test_evm_event(config: impl LineraNetConfig) -> Result<()> { use alloy_primitives::{Bytes, Log, U256}; - use alloy_sol_types::{sol, SolCall, SolValue}; - use linera_base::{ - identifiers::{GenericApplicationId, StreamId, StreamName}, - vm::EvmQuery, - }; + use alloy_sol_types::{sol, SolValue}; + use linera_base::identifiers::{GenericApplicationId, StreamId, StreamName}; use linera_execution::test_utils::solidity::get_evm_contract_path; use linera_sdk::abis::evm::EvmAbi; let _guard = INTEGRATION_TEST_GUARD.lock().await; @@ -661,9 +901,9 @@ async fn test_evm_event(config: impl LineraNetConfig) -> Result<()> { let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_log.sol")?; - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -704,10 +944,9 @@ async fn test_evm_event(config: impl LineraNetConfig) -> Result<()> { start_index += indices_and_events.len() as u32; assert_eq!(start_index, 1); - let mutation = incrementCall { input: increment }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application.run_json_query(mutation).await?; + let operation = incrementCall { input: increment }; + let operation = get_zero_operation(operation)?; + application.run_json_query(operation).await?; let indices_and_events = node_service .events_from_index(&chain, &stream_id, start_index) @@ -770,9 +1009,9 @@ async fn test_wasm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_counter.sol")?; - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let evm_application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -842,7 +1081,6 @@ async fn test_wasm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> #[test_log::test(tokio::test)] async fn test_evm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> { use alloy_sol_types::{sol, SolCall, SolValue}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -871,9 +1109,9 @@ async fn test_evm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> R constructor_argument.abi_encode() }; - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let evm_application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -902,7 +1140,7 @@ async fn test_evm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> R get_evm_contract_path("tests/fixtures/evm_call_evm_example_counter.sol")?; let nest_application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( nest_contract.clone(), nest_contract, VmRuntime::Evm, @@ -925,10 +1163,9 @@ async fn test_evm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> R let counter_value = read_evm_u64_entry(result); assert_eq!(counter_value, original_counter_value); - let mutation = nest_incrementCall { input: increment }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - nest_application.run_json_query(mutation).await?; + let operation = nest_incrementCall { input: increment }; + let operation = get_zero_operation(operation)?; + nest_application.run_json_query(operation).await?; let result = nest_application.run_json_query(query).await?; let counter_value = read_evm_u64_entry(result); @@ -951,7 +1188,6 @@ async fn test_evm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> R async fn test_evm_call_wasm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> { use alloy_sol_types::{sol, SolCall, SolValue}; use counter_no_graphql::CounterNoGraphQlAbi; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -997,9 +1233,9 @@ async fn test_evm_call_wasm_end_to_end_counter(config: impl LineraNetConfig) -> let (nest_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_call_wasm_example_counter.sol")?; - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let nest_application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( nest_contract.clone(), nest_contract, VmRuntime::Evm, @@ -1019,10 +1255,9 @@ async fn test_evm_call_wasm_end_to_end_counter(config: impl LineraNetConfig) -> let counter_value = read_evm_u64_entry(result); assert_eq!(counter_value, original_counter_value); - let mutation = nest_incrementCall { input: increment }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - nest_application.run_json_query(mutation).await?; + let operation = nest_incrementCall { input: increment }; + let operation = get_zero_operation(operation)?; + nest_application.run_json_query(operation).await?; let result = nest_application.run_json_query(query).await?; let counter_value = read_evm_u64_entry(result); @@ -1043,9 +1278,8 @@ async fn test_evm_call_wasm_end_to_end_counter(config: impl LineraNetConfig) -> #[cfg_attr(feature = "remote-net", test_case(RemoteNetTestingConfig::new(CloseChains) ; "remote_net_grpc"))] #[test_log::test(tokio::test)] async fn test_evm_execute_message_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> { - use alloy_primitives::B256; + use alloy_primitives::{B256, U256}; use alloy_sol_types::{sol, SolCall, SolValue}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -1079,13 +1313,16 @@ async fn test_evm_execute_message_end_to_end_counter(config: impl LineraNetConfi let constructor_argument = ConstructorArgs { test_value: 42 }; let constructor_argument = constructor_argument.abi_encode(); - let instantiation_argument = u64::abi_encode(&original_value); + let instantiation_argument = EvmInstantiation { + value: U256::ZERO, + argument: u64::abi_encode(&original_value), + }; let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_execute_message.sol")?; let application_id = client1 - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -1124,13 +1361,12 @@ async fn test_evm_execute_message_end_to_end_counter(config: impl LineraNetConfi let chain_id: [u64; 4] = <[u64; 4]>::from(chain2.0); let chain_id: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(chain_id); let chain_id: B256 = chain_id.into(); - let mutation = move_value_to_chainCall { + let operation = move_value_to_chainCall { chain_id, moved_value, }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application1.run_json_query(mutation).await?; + let operation = get_zero_operation(operation)?; + application1.run_json_query(operation).await?; notifications2.wait_for_bundle(chain1, None).await?; assert!(!node_service2.process_inbox(&chain2).await?.is_empty()); @@ -1162,7 +1398,6 @@ async fn test_evm_execute_message_end_to_end_counter(config: impl LineraNetConfi #[test_log::test(tokio::test)] async fn test_evm_empty_instantiate(config: impl LineraNetConfig) -> Result<()> { use alloy_sol_types::{sol, SolCall}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -1185,13 +1420,13 @@ async fn test_evm_empty_instantiate(config: impl LineraNetConfig) -> Result<()> let query = EvmQuery::Query(query); let constructor_argument = Vec::new(); - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_empty_instantiate.sol")?; let application_id = client1 - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -1238,7 +1473,6 @@ async fn test_evm_empty_instantiate(config: impl LineraNetConfig) -> Result<()> async fn test_evm_process_streams_end_to_end_counters(config: impl LineraNetConfig) -> Result<()> { use alloy_primitives::B256; use alloy_sol_types::{sol, SolCall}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u64_entry}; use linera_sdk::abis::evm::EvmAbi; @@ -1272,13 +1506,13 @@ async fn test_evm_process_streams_end_to_end_counters(config: impl LineraNetConf let query = EvmQuery::Query(query); let constructor_argument = Vec::new(); - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_example_process_streams.sol")?; let evm_application_id = client1 - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -1311,13 +1545,12 @@ async fn test_evm_process_streams_end_to_end_counters(config: impl LineraNetConf // First: subscribing to the application - let mutation = subscribeCall { + let operation = subscribeCall { chain_id: chain_id1, application_id, }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application2.run_json_query(mutation).await?; + let operation = get_zero_operation(operation)?; + application2.run_json_query(operation).await?; let result = application2.run_json_query(query.clone()).await?; let counter_value = read_evm_u64_entry(result); @@ -1325,10 +1558,9 @@ async fn test_evm_process_streams_end_to_end_counters(config: impl LineraNetConf // Second: increment the values - let mutation = increment_valueCall { increment }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application1.run_json_query(mutation).await?; + let operation = increment_valueCall { increment }; + let operation = get_zero_operation(operation)?; + application1.run_json_query(operation).await?; // Third: process the inbox on chain2 @@ -1359,9 +1591,7 @@ async fn test_evm_process_streams_end_to_end_counters(config: impl LineraNetConf #[cfg_attr(feature = "remote-net", test_case(RemoteNetTestingConfig::new(CloseChains) ; "remote_net_grpc"))] #[test_log::test(tokio::test)] async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { - use alloy_primitives::Address; - use alloy_sol_types::{sol, SolCall}; - use linera_base::{identifiers::AccountOwner, vm::EvmQuery}; + use alloy_sol_types::sol; use linera_execution::test_utils::solidity::get_evm_contract_path; use linera_sdk::abis::evm::EvmAbi; @@ -1369,11 +1599,8 @@ async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { tracing::info!("Starting test {}", test_name!()); let (mut net, client) = config.instantiate().await?; - let account_owner = client.get_owner(); - let Some(AccountOwner::Address20(address)) = account_owner else { - panic!("The owner should be of the form Some(Address20(...))"); - }; - let owner = Address::from(address); + let account_owner = client.get_owner().unwrap(); + let owner = account_owner.to_evm_address().unwrap(); let chain = client.load_wallet()?.default_chain().unwrap(); sol! { @@ -1381,14 +1608,14 @@ async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { function remote_check(address remote_address); } - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let constructor_argument = Vec::new(); // Creating the inner EVM contract let (inner_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_msg_sender_inner.sol")?; let application_id_inner = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( inner_contract.clone(), inner_contract, VmRuntime::Evm, @@ -1404,7 +1631,7 @@ async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { let (outer_contract, _dir) = get_evm_contract_path("tests/fixtures/evm_msg_sender_outer.sol")?; let application_id_outer = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( outer_contract.clone(), outer_contract, VmRuntime::Evm, @@ -1423,19 +1650,17 @@ async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { let application_inner = node_service.make_application(&chain, &application_id_inner)?; let application_outer = node_service.make_application(&chain, &application_id_outer)?; - let mutation = check_msg_senderCall { + let operation = check_msg_senderCall { remote_address: owner, }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application_inner.run_json_query(mutation).await?; + let operation = get_zero_operation(operation)?; + application_inner.run_json_query(operation).await?; - let mutation = remote_checkCall { + let operation = remote_checkCall { remote_address: evm_contract_inner, }; - let mutation = mutation.abi_encode(); - let mutation = EvmQuery::Mutation(mutation); - application_outer.run_json_query(mutation).await?; + let operation = get_zero_operation(operation)?; + application_outer.run_json_query(operation).await?; node_service.ensure_is_running()?; @@ -1454,7 +1679,6 @@ async fn test_evm_msg_sender(config: impl LineraNetConfig) -> Result<()> { async fn test_evm_linera_features(config: impl LineraNetConfig) -> Result<()> { use alloy_primitives::{B256, U256}; use alloy_sol_types::{sol, SolCall}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::get_evm_contract_path; use linera_sdk::abis::evm::EvmAbi; @@ -1480,9 +1704,9 @@ async fn test_evm_linera_features(config: impl LineraNetConfig) -> Result<()> { let (contract, _dir) = get_evm_contract_path("tests/fixtures/evm_test_linera_features.sol")?; let constructor_argument = Vec::new(); - let instantiation_argument = Vec::new(); + let instantiation_argument = EvmInstantiation::default(); let application_id = client - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( contract.clone(), contract, VmRuntime::Evm, @@ -1531,9 +1755,9 @@ async fn test_evm_linera_features(config: impl LineraNetConfig) -> Result<()> { // Checking authenticated owner/caller_id - let mutation = test_authenticated_owner_caller_idCall {}; - let mutation = EvmQuery::Mutation(mutation.abi_encode()); - application.run_json_query(mutation).await?; + let operation = test_authenticated_owner_caller_idCall {}; + let operation = get_zero_operation(operation)?; + application.run_json_query(operation).await?; // Testing the chain balance @@ -1628,7 +1852,6 @@ async fn test_wasm_end_to_end_counter(config: impl LineraNetConfig) -> Result<() async fn test_evm_erc20_shared(config: impl LineraNetConfig) -> Result<()> { use alloy_primitives::{B256, U256}; use alloy_sol_types::{sol, SolCall, SolValue}; - use linera_base::vm::EvmQuery; use linera_execution::test_utils::solidity::{get_evm_contract_path, read_evm_u256_entry}; use linera_sdk::abis::evm::EvmAbi; let _guard = INTEGRATION_TEST_GUARD.lock().await; @@ -1662,12 +1885,15 @@ async fn test_evm_erc20_shared(config: impl LineraNetConfig) -> Result<()> { let constructor_argument = ConstructorArgs { the_supply }; let constructor_argument = constructor_argument.abi_encode(); - let instantiation_argument = U256::abi_encode(&the_supply); + let instantiation_argument = EvmInstantiation { + value: U256::ZERO, + argument: U256::abi_encode(&the_supply), + }; let (evm_contract, _dir) = get_evm_contract_path("tests/fixtures/erc20_shared.sol")?; let application_id = client1 - .publish_and_create::, Vec>( + .publish_and_create::, EvmInstantiation>( evm_contract.clone(), evm_contract, VmRuntime::Evm, @@ -1698,12 +1924,11 @@ async fn test_evm_erc20_shared(config: impl LineraNetConfig) -> Result<()> { // Transferring to another user and checking the balances. - let mutation = transferCall { + let operation = transferCall { to: address2, value: transfer1, }; - let mutations = vec![mutation.abi_encode(); num_operations]; - let query = EvmQuery::Mutations(mutations); + let query = get_zero_operations(operation, num_operations)?; let time_start = Instant::now(); application1.run_json_query(query).await?; let average_time = (time_start.elapsed().as_millis() as f64) / (num_operations as f64); @@ -1728,13 +1953,13 @@ async fn test_evm_erc20_shared(config: impl LineraNetConfig) -> Result<()> { let chain_id: [u64; 4] = <[u64; 4]>::from(chain2.0); let chain_id: [u8; 32] = linera_base::crypto::u64_array_to_be_bytes(chain_id); let chain_id: B256 = chain_id.into(); - let mutation = transferToChainCall { + let operation = transferToChainCall { chain_id, destination: address2, value: transfer2, }; - let mutation = EvmQuery::Mutation(mutation.abi_encode()); - application1.run_json_query(mutation).await?; + let operation = get_zero_operation(operation)?; + application1.run_json_query(operation).await?; notifications2.wait_for_bundle(chain1, None).await?; assert!(!node_service2.process_inbox(&chain2).await?.is_empty());