diff --git a/.gitignore b/.gitignore index f7ad19dd5b..9a7401d499 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ e2e/sway/**/.gitignore .env output_changelog.md + +.idea/ diff --git a/Cargo.toml b/Cargo.toml index c10e696d86..5822b1b236 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ tar = { version = "0.4", default-features = false } tempfile = { version = "3.8.1", default-features = false } thiserror = { version = "1.0.50", default-features = false } tokio = { version = "1.34.0", default-features = false } -tracing = "0.1.40" +tracing = "0.1" trybuild = "1.0.85" uint = { version = "0.9.5", default-features = false } which = { version = "6.0.0", default-features = false } @@ -105,13 +105,13 @@ fuel-core-services = { version = "0.43.0", default-features = false } fuel-core-types = { version = "0.43.0", default-features = false } # Dependencies from the `fuel-vm` repository: -fuel-asm = { version = "0.60.0" } -fuel-crypto = { version = "0.60.0" } -fuel-merkle = { version = "0.60.0" } -fuel-storage = { version = "0.60.0" } -fuel-tx = { version = "0.60.0" } -fuel-types = { version = "0.60.0" } -fuel-vm = { version = "0.60.0" } +fuel-asm = { version = "=0.60.0" } +fuel-crypto = { version = "=0.60.0" } +fuel-merkle = { version = "=0.60.0" } +fuel-storage = { version = "=0.60.0" } +fuel-tx = { version = "=0.60.0" } +fuel-types = { version = "=0.60.0" } +fuel-vm = { version = "=0.60.0" } # Workspace projects fuels = { version = "0.71.0", path = "./packages/fuels", default-features = false } diff --git a/e2e/Forc.toml b/e2e/Forc.toml index 629bf01643..8f772266b5 100644 --- a/e2e/Forc.toml +++ b/e2e/Forc.toml @@ -42,6 +42,7 @@ members = [ 'sway/predicates/predicate_blobs', 'sway/predicates/predicate_configurables', 'sway/predicates/predicate_witnesses', + 'sway/predicates/read_only_verified', 'sway/predicates/signatures', 'sway/predicates/swap', 'sway/predicates/predicate_tx_input_output', diff --git a/e2e/sway/predicates/read_only_verified/Forc.toml b/e2e/sway/predicates/read_only_verified/Forc.toml new file mode 100644 index 0000000000..11d45e00ea --- /dev/null +++ b/e2e/sway/predicates/read_only_verified/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["human"] +entry = "main.sw" +license = "Apache-2.0" +name = "read_only_verified" + +[dependencies] +std = { path = "../../../../../sway/sway-lib-std" } \ No newline at end of file diff --git a/e2e/sway/predicates/read_only_verified/src/main.sw b/e2e/sway/predicates/read_only_verified/src/main.sw new file mode 100644 index 0000000000..15f0dcaf25 --- /dev/null +++ b/e2e/sway/predicates/read_only_verified/src/main.sw @@ -0,0 +1,22 @@ +predicate; + +use std::{inputs::*}; + +struct SomeData { + value: u64, +} + +fn first_input_is_read_only_verified() -> bool { + let first_input_type = input_type(0).unwrap(); + first_input_type == Input::ReadOnly(ReadOnlyInput::DataCoinPredicate) +} + +fn read_only_input_value_matches_predicate_data(predicate_data: SomeData) -> bool { + let input_data = input_data_coin_data::(0).unwrap(); + predicate_data.value == input_data.value +} + +fn main(predicate_data: SomeData) -> bool { + first_input_is_read_only_verified() + && read_only_input_value_matches_predicate_data(predicate_data) +} diff --git a/e2e/tests/predicates.rs b/e2e/tests/predicates.rs index 88e823b5f2..381caeff25 100644 --- a/e2e/tests/predicates.rs +++ b/e2e/tests/predicates.rs @@ -1,5 +1,7 @@ -use std::default::Default; +#![allow(non_snake_case)] +use std::default::Default; +use fuel_asm::{op, RegId}; use fuels::{ accounts::signers::private_key::PrivateKeySigner, core::{ @@ -9,7 +11,7 @@ use fuels::{ prelude::*, programs::executable::Executable, types::{ - coin::{Coin, DataCoin}, + coin::Coin, coin_type::CoinType, input::Input, message::Message, @@ -17,6 +19,7 @@ use fuels::{ }, }; use rand::thread_rng; +use fuels::types::coin_type::ReadOnly; async fn assert_address_balance( address: &Bech32Address, @@ -287,6 +290,109 @@ async fn data_coins() -> Result<()> { Ok(()) } +#[tokio::test] +async fn read_only_coin__if_predicate_data_matches_read_only_data__succeed() -> Result<()> { + let signer = PrivateKeySigner::random(&mut thread_rng()); + abigen!(Predicate( + name = "MyPredicate", + abi = "e2e/sway/predicates/read_only_verified/out/release/read_only_verified-abi.json" + )); + let predicate = Predicate::load_from("sway/predicates/read_only_verified/out/release/read_only_verified.bin")?; + + let true_predicate_code: Vec = vec![op::ret(RegId::ONE)].into_iter().collect(); + let true_predicate = Predicate::from_code(true_predicate_code); + + let asset_id = AssetId::zeroed(); + + let value = 1; + let data = SomeData { + value, + }; + let encoded_data = ABIEncoder::default() + .encode(&[data.into_token()])?; + + let amount_predicate_coin = 32; + let amount_true_predicate_coin = 1000; + let mut predicate_coins = + setup_single_asset_coins(predicate.address(), asset_id, 1, amount_predicate_coin); + let mut true_predicate_coins = setup_single_asset_data_coins( + true_predicate.address(), + asset_id, + 1, + amount_true_predicate_coin, + encoded_data.clone() + ); + let messages = vec![]; + + let provider = setup_test_provider2( + predicate_coins.clone(), + true_predicate_coins.clone(), + messages, + None, + None, + ) + .await?; + let wallet = Wallet::new(signer, provider.clone()); + + // given + let coin_input = Input::resource_predicate( + CoinType::Coin(predicate_coins.pop().unwrap()), + predicate.code().to_vec(), + encoded_data, + ); + let read_only_input = Input::resource_predicate( + CoinType::ReadOnly(ReadOnly::DataCoinPredicate(true_predicate_coins.pop().unwrap())), + true_predicate.code().to_vec(), + vec![], + ); + + let outputs = vec![ + Output::change(predicate.address().into(), 0, asset_id), + ]; + + let mut tb = ScriptTransactionBuilder::prepare_transfer( + vec![read_only_input, coin_input], + outputs, + TxPolicies::default(), + ); + tb.add_signer(wallet.signer().clone())?; + + let tx = tb.build(&provider).await.unwrap(); + let chain_id = provider.consensus_parameters().await?.chain_id(); + + let tx_id = tx.id(chain_id); + + dbg!(&tx.inputs()); + + // when + let tx_status = provider + .send_transaction_and_await_commit(tx) + .await + .unwrap(); + let fee = tx_status.total_fee(); + + dbg!(&tx_status); + + // then + let tx_from_client = match provider + .get_transaction_by_id(&tx_id) + .await? + .unwrap() + .transaction + { + TransactionType::Script(script) => script, + _ => panic!("nandarou"), + }; + + let outputs = tx_from_client.outputs(); + let change_output = outputs + .iter() + .find(|output| output.is_change()) + .expect("Expected a change output"); + assert_eq!(change_output.amount().unwrap(), amount_predicate_coin - fee); + Ok(()) +} + #[tokio::test] async fn transfer_coins_and_messages_to_predicate() -> Result<()> { let num_coins = 16; diff --git a/packages/fuels-accounts/src/provider/retryable_client.rs b/packages/fuels-accounts/src/provider/retryable_client.rs index 0ea568e884..385dc5f1d5 100644 --- a/packages/fuels-accounts/src/provider/retryable_client.rs +++ b/packages/fuels-accounts/src/provider/retryable_client.rs @@ -230,7 +230,8 @@ impl RetryableClient { } pub async fn balance(&self, owner: &Address, asset_id: Option<&AssetId>) -> RequestResult { - self.wrap(|| self.client.balance(owner, asset_id)).await + // TODO: actually solve; this is just a band-aid + self.wrap(|| self.client.balance(owner, asset_id)).await.map(|x| x as u64) } pub async fn contract_balance( diff --git a/packages/fuels-core/src/types/bech32.rs b/packages/fuels-core/src/types/bech32.rs index 62a356b667..7306a59189 100644 --- a/packages/fuels-core/src/types/bech32.rs +++ b/packages/fuels-core/src/types/bech32.rs @@ -5,7 +5,7 @@ use std::{ use bech32::{FromBase32, ToBase32, Variant::Bech32m}; use fuel_tx::{Address, Bytes32, ContractId, ContractIdExt}; -use fuel_types::AssetId; +use fuel_types::{AssetId, SubAssetId}; use crate::types::{ Bits256, @@ -143,7 +143,7 @@ impl From for Bech32ContractId { impl Bech32ContractId { /// Creates an `AssetId` from the `Bech32ContractId` and `sub_id`. pub fn asset_id(&self, sub_id: &Bits256) -> AssetId { - let sub_id = Bytes32::from(sub_id.0); + let sub_id = SubAssetId::from(sub_id.0); ContractId::from(self).asset_id(&sub_id) } } diff --git a/packages/fuels-core/src/types/transaction_builders.rs b/packages/fuels-core/src/types/transaction_builders.rs index 753881818e..4ebdfd6fd5 100644 --- a/packages/fuels-core/src/types/transaction_builders.rs +++ b/packages/fuels-core/src/types/transaction_builders.rs @@ -463,7 +463,7 @@ macro_rules! impl_tx_builder_trait { } pub(crate) use impl_tx_builder_trait; - +use crate::types::coin_type::ReadOnly as ClientReadOnly; use super::coin::DataCoin; pub(crate) fn estimate_max_fee_w_tolerance( @@ -1404,6 +1404,22 @@ fn resolve_signed_resource( create_coin_message_input(message, num_witnesses + *witness_idx_offset as u16) }) } + CoinType::ReadOnly(inner) => match inner { + ClientReadOnly::DataCoinPredicate(data_coin) => { + let owner = &data_coin.owner; + + unresolved_witness_indexes + .owner_to_idx_offset + .get(owner) + .ok_or(error_transaction!( + Builder, + "signature missing for coin with owner: `{owner:?}`" + )) + .map(|witness_idx_offset| { + create_data_coin_input(data_coin, num_witnesses + *witness_idx_offset as u16) + }) + } + } CoinType::Unknown => Err(error_transaction!( Builder, "can not resolve `CoinType::Unknown`" @@ -1419,6 +1435,13 @@ fn resolve_predicate_resource( match resource { CoinType::Coin(coin) => Ok(create_coin_predicate(coin, code, data)), CoinType::DataCoin(coin) => Ok(create_data_coin_predicate(coin, code, data)), + CoinType::ReadOnly(read_only) => { + match read_only { + ClientReadOnly::DataCoinPredicate(data_coin) => { + Ok(create_read_only_data_coin_predicate(data_coin, code, data)) + } + } + } CoinType::Message(message) => Ok(create_coin_message_predicate(message, code, data)), CoinType::Unknown => Err(error_transaction!( Builder, @@ -1502,6 +1525,46 @@ pub fn create_data_coin_predicate( ) } +pub fn create_read_only_data_coin_predicate( + coin: DataCoin, + code: Vec, + predicate_data: Vec, +) -> FuelInput { + // let inner = FuelInput::data_coin_predicate( + // coin.utxo_id, + // coin.owner.into(), + // coin.amount, + // coin.asset_id, + // TxPointer::default(), + // 0u64, + // code, + // predicate_data, + // coin.data, + // ); + // let inner = DataCoinPredicate { + // utxo_id: coin.utxo_id, + // owner: coin.owner.into(), + // amount: coin.amount, + // asset_id: coin.asset_id, + // tx_pointer: TxPointer::default(), + // witness_index: Empty::new(), + // predicate_gas_used: 0, + // predicate: PredicateCode {bytes: code}, + // predicate_data, + // data: coin.data, + // }; + // FuelInput::ReadOnly(ClientReadOnly::DataCoinPredicate(inner)) + FuelInput::read_only_data_coin_predicate(coin.utxo_id, coin.owner.into() + , coin.amount + , coin.asset_id + , TxPointer::default() + , 0u64 + , code + , predicate_data + , coin.data + ) +} + pub fn create_coin_message_predicate( message: Message, code: Vec, diff --git a/packages/fuels-core/src/types/wrappers/coin.rs b/packages/fuels-core/src/types/wrappers/coin.rs index 259be1ae63..a7e96947e6 100644 --- a/packages/fuels-core/src/types/wrappers/coin.rs +++ b/packages/fuels-core/src/types/wrappers/coin.rs @@ -3,6 +3,7 @@ use fuel_core_chain_config::{CoinConfig, ConfigCoin, ConfigDataCoin}; use fuel_core_client::client::types::{ coins::Coin as ClientCoin, + coins::DataCoin as ClientDataCoin, primitives::{AssetId, UtxoId}, }; @@ -90,3 +91,17 @@ impl From for CoinConfig { }) } } + +impl From for DataCoin { + fn from(coin: ClientDataCoin) -> Self { + Self { + amount: coin.amount, + block_created: coin.block_created, + asset_id: coin.asset_id, + utxo_id: coin.utxo_id, + owner: Bech32Address::from(coin.owner), + status: CoinStatus::Unspent, + data: coin.data, + } + } +} diff --git a/packages/fuels-core/src/types/wrappers/coin_type.rs b/packages/fuels-core/src/types/wrappers/coin_type.rs index cd94efa29a..b1f2f863c8 100644 --- a/packages/fuels-core/src/types/wrappers/coin_type.rs +++ b/packages/fuels-core/src/types/wrappers/coin_type.rs @@ -12,14 +12,21 @@ use super::coin::DataCoin; pub enum CoinType { Coin(Coin), DataCoin(DataCoin), + ReadOnly(ReadOnly), Message(Message), Unknown, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ReadOnly { + DataCoinPredicate(DataCoin) +} + impl From for CoinType { fn from(client_resource: ClientCoinType) -> Self { match client_resource { ClientCoinType::Coin(coin) => CoinType::Coin(coin.into()), + ClientCoinType::DataCoin(data_coin) => CoinType::DataCoin(data_coin.into()), ClientCoinType::MessageCoin(message) => CoinType::Message(message.into()), ClientCoinType::Unknown => CoinType::Unknown, } @@ -32,6 +39,11 @@ impl CoinType { CoinType::Coin(coin) => Some(CoinTypeId::UtxoId(coin.utxo_id)), CoinType::DataCoin(coin) => Some(CoinTypeId::UtxoId(coin.utxo_id)), CoinType::Message(message) => Some(CoinTypeId::Nonce(message.nonce)), + CoinType::ReadOnly(read_only) => { + match read_only { + ReadOnly::DataCoinPredicate(data_coin) => Some(CoinTypeId::UtxoId(data_coin.utxo_id)), + } + } CoinType::Unknown => None, } } @@ -40,6 +52,11 @@ impl CoinType { match self { CoinType::Coin(coin) => coin.amount, CoinType::DataCoin(coin) => coin.amount, + CoinType::ReadOnly(read_only) => { + match read_only { + ReadOnly::DataCoinPredicate(data_coin) => data_coin.amount, + } + } CoinType::Message(message) => message.amount, CoinType::Unknown => 0, } @@ -49,6 +66,11 @@ impl CoinType { match self { CoinType::Coin(coin) => Some(coin.asset_id), CoinType::DataCoin(coin) => Some(coin.asset_id), + CoinType::ReadOnly(read_only) => { + match read_only { + ReadOnly::DataCoinPredicate(data_coin) => Some(data_coin.asset_id), + } + } CoinType::Message(_) => None, CoinType::Unknown => None, } @@ -58,6 +80,11 @@ impl CoinType { match self { CoinType::Coin(coin) => Some(coin.asset_id), CoinType::DataCoin(coin) => Some(coin.asset_id), + CoinType::ReadOnly(read_only) => { + match read_only { + ReadOnly::DataCoinPredicate(data_coin) => Some(data_coin.asset_id), + } + } CoinType::Message(_) => Some(base_asset_id), CoinType::Unknown => None, } @@ -67,6 +94,11 @@ impl CoinType { match self { CoinType::Coin(coin) => Some(&coin.owner), CoinType::DataCoin(coin) => Some(&coin.owner), + CoinType::ReadOnly(read_only) => { + match read_only { + ReadOnly::DataCoinPredicate(data_coin) => Some(&data_coin.owner), + } + } CoinType::Message(message) => Some(&message.recipient), CoinType::Unknown => None, } diff --git a/packages/fuels-core/src/types/wrappers/transaction.rs b/packages/fuels-core/src/types/wrappers/transaction.rs index 454ce40578..6ab84c2cca 100644 --- a/packages/fuels-core/src/types/wrappers/transaction.rs +++ b/packages/fuels-core/src/types/wrappers/transaction.rs @@ -188,7 +188,8 @@ impl TxPolicies { } use fuel_tx::field::{BytecodeWitnessIndex, Salt, StorageSlots}; - +use fuel_tx::input::coin::{UnverifiedCoin, UnverifiedDataCoin}; +use fuel_tx::input::ReadOnly; use crate::types::coin_type_id::CoinTypeId; #[derive(Debug, Clone)] @@ -322,7 +323,14 @@ pub fn extract_owner_or_recipient(input: &Input) -> Option { Input::CoinSigned(CoinSigned { owner, .. }) | Input::CoinPredicate(CoinPredicate { owner, .. }) | Input::DataCoinSigned(DataCoinSigned { owner, .. }) - | Input::DataCoinPredicate(DataCoinPredicate { owner, .. }) => Some(owner), + | Input::DataCoinPredicate(DataCoinPredicate { owner, .. }) + | Input::ReadOnly(ReadOnly::Coin(UnverifiedCoin { owner, .. })) + | Input::ReadOnly(ReadOnly::DataCoin(UnverifiedDataCoin { owner, .. })) => { + Some(owner) + } + | Input::ReadOnly(ReadOnly::CoinPredicate(CoinPredicate { owner, .. })) + | Input::ReadOnly(ReadOnly::DataCoinPredicate(DataCoinPredicate { owner, .. })) + => Some(owner), Input::MessageCoinSigned(MessageCoinSigned { recipient, .. }) | Input::MessageCoinPredicate(MessageCoinPredicate { recipient, .. }) | Input::MessageDataSigned(MessageDataSigned { recipient, .. }) diff --git a/packages/fuels-test-helpers/src/lib.rs b/packages/fuels-test-helpers/src/lib.rs index 8ab8c27c11..4c1da4e2ba 100644 --- a/packages/fuels-test-helpers/src/lib.rs +++ b/packages/fuels-test-helpers/src/lib.rs @@ -227,7 +227,7 @@ pub async fn setup_test_provider2( ..StateConfig::local_testnet() }; - let srv = FuelService::start(node_config, chain_config, state_config).await?; + let srv = FuelService::start(node_config, chain_config, state_config).await.unwrap(); let address = srv.bound_address();