Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
] }
Expand Down
33 changes: 16 additions & 17 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
8 changes: 5 additions & 3 deletions examples/call-evm-counter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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)
}
}
Expand Down
24 changes: 24 additions & 0 deletions linera-base/src/data_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ impl From<Amount> 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<U256> for Amount {
type Error = AmountConversionError;
fn try_from(value: U256) -> Result<Amount, Self::Error> {
let value = u128::try_from(&value).map_err(|_| AmountConversionError(value))?;
Ok(Amount(value))
}
}

/// A block height to identify blocks in a chain.
#[derive(
Eq,
Expand Down Expand Up @@ -1590,6 +1604,8 @@ mod metrics {
mod tests {
use std::str::FromStr;

use alloy_primitives::U256;

use super::{Amount, BlobContent};
use crate::identifiers::BlobType;

Expand Down Expand Up @@ -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);
}
}
49 changes: 36 additions & 13 deletions linera-base/src/identifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ impl AccountOwner {
}
}

#[cfg(with_revm)]
impl From<Address> for AccountOwner {
fn from(address: Address) -> Self {
let address = address.into_array();
AccountOwner::Address20(address)
}
}

#[cfg(with_testing)]
impl From<CryptoHash> for AccountOwner {
fn from(address: CryptoHash) -> Self {
Expand Down Expand Up @@ -416,7 +424,12 @@ impl GenericApplicationId {

impl<A> From<ApplicationId<A>> for AccountOwner {
fn from(app_id: ApplicationId<A>) -> 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)
}
}
}

Expand Down Expand Up @@ -966,6 +979,14 @@ impl<A> ApplicationId<A> {
}
}

impl<A> ApplicationId<A> {
/// 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<Address> for ApplicationId {
fn from(address: Address) -> ApplicationId {
Expand All @@ -991,18 +1012,6 @@ impl<A> ApplicationId<A> {
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)]
Expand Down Expand Up @@ -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);
}
}
45 changes: 43 additions & 2 deletions linera-base/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -63,7 +65,46 @@ pub enum EvmQuery {
/// A read-only query.
Query(Vec<u8>),
/// A request to schedule an operation that can mutate the application state.
Mutation(Vec<u8>),
Operation(Vec<u8>),
/// A request to schedule operations that can mutate the application state.
Mutations(Vec<Vec<u8>>),
Operations(Vec<Vec<u8>>),
}

/// 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<u8>,
}

impl EvmOperation {
/// An EVM transaction with a specified amount and function input.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// An EVM transaction with a specified amount and function input.
/// Creates an EVM transaction with a specified amount and function input.

(And maybe it should be "EVM operation" now?)

pub fn new(amount: Amount, argument: Vec<u8>) -> Self {
Self {
value: amount.into(),
argument,
}
}

/// Convert the input to a `Vec<u8>` if possible.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Convert the input to a `Vec<u8>` if possible.
/// Converts the input to a `Vec<u8>` if possible.

pub fn to_bytes(&self) -> Result<Vec<u8>, bcs::Error> {
bcs::to_bytes(&self)
}

/// Creates an `EvmQuery` from the input.
pub fn to_evm_query(&self) -> Result<EvmQuery, bcs::Error> {
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<u8>,
}
Comment on lines +102 to 110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it important to have this as a separate type? Could instantiation simply be considered a specific kind of operation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can.
However, Linera does allow the separation between the Operation and Instantiation types. We take advantage of it.
So, of course, we could identify the type, but we would gain nothing from doing that. Only losing clarity.

37 changes: 37 additions & 0 deletions linera-execution/solidity/Linera.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ library Linera {
return ChainId(entry.value.value);
}

function chainid_to(Linera.ChainId memory entry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Below (and in Rust code everywhere) we use chain_id, not chainid.

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
Expand Down Expand Up @@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Why "2"?)

LineraTypes.AccountOwner memory owner2 = accountowner_to(account_i.owner);
return LineraTypes.Account(chain_id2, owner2);
}

struct AccountOwnerBalance {
AccountOwner account_owner;
uint256 balance;
Expand Down Expand Up @@ -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) {
Expand Down
Loading
Loading