Skip to content

feat: add support for read only input #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: feat/add-data-utxo
Choose a base branch
from
Draft
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -22,3 +22,5 @@ e2e/sway/**/.gitignore
.env

output_changelog.md

.idea/
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
1 change: 1 addition & 0 deletions e2e/Forc.toml
Original file line number Diff line number Diff line change
@@ -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',
8 changes: 8 additions & 0 deletions e2e/sway/predicates/read_only_verified/Forc.toml
Original file line number Diff line number Diff line change
@@ -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" }
22 changes: 22 additions & 0 deletions e2e/sway/predicates/read_only_verified/src/main.sw
Original file line number Diff line number Diff line change
@@ -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::<SomeData>(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)
}
110 changes: 108 additions & 2 deletions e2e/tests/predicates.rs
Original file line number Diff line number Diff line change
@@ -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,14 +11,15 @@ use fuels::{
prelude::*,
programs::executable::Executable,
types::{
coin::{Coin, DataCoin},
coin::Coin,
coin_type::CoinType,
input::Input,
message::Message,
output::Output,
},
};
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<u8> = 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;
3 changes: 2 additions & 1 deletion packages/fuels-accounts/src/provider/retryable_client.rs
Original file line number Diff line number Diff line change
@@ -230,7 +230,8 @@ impl RetryableClient {
}

pub async fn balance(&self, owner: &Address, asset_id: Option<&AssetId>) -> RequestResult<u64> {
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(
4 changes: 2 additions & 2 deletions packages/fuels-core/src/types/bech32.rs
Original file line number Diff line number Diff line change
@@ -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<ContractId> 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)
}
}
65 changes: 64 additions & 1 deletion packages/fuels-core/src/types/transaction_builders.rs
Original file line number Diff line number Diff line change
@@ -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<T: Chargeable>(
@@ -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<u8>,
predicate_data: Vec<u8>,
) -> 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<u8>,
15 changes: 15 additions & 0 deletions packages/fuels-core/src/types/wrappers/coin.rs
Original file line number Diff line number Diff line change
@@ -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<DataCoin> for CoinConfig {
})
}
}

impl From<ClientDataCoin> 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,
}
}
}
Loading