Skip to content

feat: add support for the data coin #1637

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 6 commits into
base: master
Choose a base branch
from
Draft
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
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,18 @@ fuels-macros = { version = "0.71.0", path = "./packages/fuels-macros", default-f
fuels-programs = { version = "0.71.0", path = "./packages/fuels-programs", default-features = false }
fuels-test-helpers = { version = "0.71.0", path = "./packages/fuels-test-helpers", default-features = false }
versions-replacer = { version = "0.71.0", path = "./scripts/versions-replacer", default-features = false }

[patch.crates-io]
fuel-core-client = { path = "../fuel-core/crates/client"}
fuel-core-types = { path = "../fuel-core/crates/types"}
fuel-core-chain-config = { path = "../fuel-core/crates/chain-config"}
fuel-core-poa = { path = "../fuel-core/crates/services/consensus_module/poa"}
fuel-core-services = { path = "../fuel-core/crates/services"}

fuel-asm = { path = "../fuel-vm/fuel-asm" }
fuel-crypto = { path = "../fuel-vm/fuel-crypto" }
fuel-merkle = { path = "../fuel-vm/fuel-merkle" }
fuel-storage = { path = "../fuel-vm/fuel-storage" }
fuel-tx = { path = "../fuel-vm/fuel-tx" }
fuel-types = { path = "../fuel-vm/fuel-types" }
fuel-vm = { path = "../fuel-vm/fuel-vm" }
2 changes: 2 additions & 0 deletions e2e/Forc.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ members = [
'sway/logs/script_logs',
'sway/logs/script_with_contract_logs',
'sway/predicates/basic_predicate',
'sway/predicates/data_coins',
'sway/predicates/predicate_blobs',
'sway/predicates/predicate_configurables',
'sway/predicates/predicate_witnesses',
Expand Down Expand Up @@ -118,3 +119,4 @@ members = [
'sway/types/scripts/script_u256',
'sway/types/scripts/script_vectors',
]

8 changes: 8 additions & 0 deletions e2e/sway/predicates/data_coins/Forc.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[project]
authors = ["Fuel Labs <[email protected]>"]
entry = "main.sw"
license = "Apache-2.0"
name = "data_coins"

[dependencies]
std = { path = "../../../../../sway/sway-lib-std" }
61 changes: 61 additions & 0 deletions e2e/sway/predicates/data_coins/src/main.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
predicate;

use std::{inputs::*, outputs::*};

struct DataCoinConfig {
num_participants: u64,
}

fn main(_unused: DataCoinConfig) -> bool {
let input_count = input_count();
let output_count = output_count();
match (input_count, output_count) {
// transfer some amount from predicate coin
// to data coin and return change to predicate
(1, 2) => { //(predicate_coin, data_coin and predicate change)
// Check:
// - predicate coin amount is bigger than data_coin amount
// - data_coin data can be decoded into `DataCoinConfig`
// - `DataCoinConfig` `num_praticipants == 1` (initial config creation)
let predicate_coin_amount = input_amount(0).unwrap();
let data_coin_amount = output_amount(0).unwrap();

if predicate_coin_amount < data_coin_amount {
return false;
}

let datacoin_config = output_data_coin_data::<DataCoinConfig>(0).unwrap();

if datacoin_config.num_participants != 1 {
return false;
}

return true;
},
// update input data coin with new amount from predicate coin and incremented `num_participants`
(2, 2) => { //(data_coin and predicate_coin, data_coin and predicate change)
// Check:
// - predicate coin amount is bigger than (output_data_coin_amount - input_data_coin_amount)
// - data_coin data can be decoded into `DataCoinConfig`
// - output `DataCoinConfig` `num_praticipants` is incremented by 1
let input_data_coin_amount = input_amount(0).unwrap();
let predicate_coin_amount = input_amount(1).unwrap();
let output_data_coin_amount = output_amount(0).unwrap();

if predicate_coin_amount < (output_data_coin_amount - input_data_coin_amount)
{
return false;
}

let input_num_participants = input_data_coin_data::<DataCoinConfig>(0).unwrap().num_participants;
let output_num_participants = output_data_coin_data::<DataCoinConfig>(0).unwrap().num_participants;

if (output_num_participants - input_num_participants) != 1 {
return false;
}

return true;
},
_ => return false,
}
}
176 changes: 175 additions & 1 deletion e2e/tests/predicates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ use fuels::{
},
prelude::*,
programs::executable::Executable,
types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output},
types::{
coin::{Coin, DataCoin},
coin_type::CoinType,
input::Input,
message::Message,
output::Output,
},
};
use rand::thread_rng;

Expand Down Expand Up @@ -113,6 +119,174 @@ async fn setup_predicate_test(
))
}

#[tokio::test]
async fn data_coins() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/data_coins/out/release/data_coins-abi.json"
));

let predicate = Predicate::load_from("sway/predicates/data_coins/out/release/data_coins.bin")?;

let num_coins = 2;
let num_messages = 0;
let amount = 164;

let signer = PrivateKeySigner::random(&mut thread_rng());

let (coins, messages, asset_id) =
get_test_coins_and_messages(signer.address(), num_coins, num_messages, amount, 0);

let encoded_data = ABIEncoder::default().encode(&[DataCoinConfig {
num_participants: 5,
}
.into_token()])?;

let amount_data_coin = 16;
let mut data_coins = vec![setup_single_data_coin(
predicate.address(),
asset_id,
amount_data_coin,
encoded_data,
)];

let amount_predicate_coin = 32;
let predicate_coins =
setup_single_asset_coins(predicate.address(), asset_id, 2, amount_predicate_coin);
let [predicate_coin_1, predicate_coin_2] = predicate_coins.clone().try_into().unwrap();

let provider = setup_test_provider2(
[coins, predicate_coins].concat(),
data_coins.clone(),
messages,
None,
None,
)
.await?;
let chain_id = provider.consensus_parameters().await?.chain_id();

let wallet = Wallet::new(signer, provider.clone());

let predicate = predicate.with_provider(provider.clone());

{
let predicate_input_coin = Input::resource_predicate(
CoinType::Coin(predicate_coin_1),
predicate.code().to_vec(),
predicate.data().to_vec(),
);

let data_coin_data = DataCoinConfig {
num_participants: 1,
};
let encoded_data = ABIEncoder::default().encode(&[data_coin_data.into_token()])?;

let outputs = vec![
Output::data_coin(
predicate.address().into(),
amount_data_coin,
asset_id,
encoded_data,
),
Output::change(predicate.address().into(), 0, asset_id),
];

let mut tb = ScriptTransactionBuilder::prepare_transfer(
vec![predicate_input_coin],
outputs,
TxPolicies::default(),
);
tb.add_signer(wallet.signer().clone())?;

let tx = tb.build(&provider).await?;

let tx_id = tx.id(chain_id);

dbg!(&tx.inputs());

let tx_status = provider.send_transaction_and_await_commit(tx).await?;

dbg!(&tx_status);

let tx_from_client = match provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
{
TransactionType::Script(script) => script,
_ => panic!("nani"),
};

dbg!(&tx_from_client.outputs());
}
// data coin input data coin output
{
dbg!("2");
let data_coin = data_coins.pop().unwrap();
let data_coin_input = Input::resource_predicate(
CoinType::DataCoin(data_coin),
predicate.code().to_vec(),
predicate.data().to_vec(),
);
let predicate_input_coin = Input::resource_predicate(
CoinType::Coin(predicate_coin_2),
predicate.code().to_vec(),
predicate.data().to_vec(),
);

let output_data_coin_data = DataCoinConfig {
num_participants: 6,
};
let output_encoded_data =
ABIEncoder::default().encode(&[output_data_coin_data.into_token()])?;

let outputs = vec![
Output::data_coin(
predicate.address().into(),
amount_data_coin * 2,
asset_id,
output_encoded_data,
),
Output::change(predicate.address().into(), 0, asset_id),
];

let mut tb = ScriptTransactionBuilder::prepare_transfer(
vec![data_coin_input, predicate_input_coin],
outputs,
TxPolicies::default(),
);
tb.add_signer(wallet.signer().clone())?;

let tx = tb.build(&provider).await.unwrap();

let tx_id = tx.id(chain_id);

dbg!(&tx.inputs());

let tx_status = provider
.send_transaction_and_await_commit(tx)
.await
.unwrap();

dbg!(&tx_status);

let tx_from_client = match provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
{
TransactionType::Script(script) => script,
_ => panic!("nani"),
};

dbg!(&tx_from_client.outputs());
}

Ok(())
}

#[tokio::test]
async fn transfer_coins_and_messages_to_predicate() -> Result<()> {
let num_coins = 16;
Expand Down
54 changes: 48 additions & 6 deletions packages/fuels-core/src/types/transaction_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ macro_rules! impl_tx_builder_trait {

pub(crate) use impl_tx_builder_trait;

use super::coin::DataCoin;

pub(crate) fn estimate_max_fee_w_tolerance<T: Chargeable>(
tx: T,
tolerance: f32,
Expand Down Expand Up @@ -1374,6 +1376,20 @@ fn resolve_signed_resource(
create_coin_input(coin, num_witnesses + *witness_idx_offset as u16)
})
}
CoinType::DataCoin(coin) => {
let owner = &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(coin, num_witnesses + *witness_idx_offset as u16)
})
}
CoinType::Message(message) => {
let recipient = &message.recipient;

Expand Down Expand Up @@ -1401,7 +1417,8 @@ fn resolve_predicate_resource(
data: Vec<u8>,
) -> Result<FuelInput> {
match resource {
CoinType::Coin(coin) => Ok(create_coin_predicate(coin.asset_id, coin, code, data)),
CoinType::Coin(coin) => Ok(create_coin_predicate(coin, code, data)),
CoinType::DataCoin(coin) => Ok(create_data_coin_predicate(coin, code, data)),
CoinType::Message(message) => Ok(create_coin_message_predicate(message, code, data)),
CoinType::Unknown => Err(error_transaction!(
Builder,
Expand All @@ -1421,6 +1438,18 @@ pub fn create_coin_input(coin: Coin, witness_index: u16) -> FuelInput {
)
}

pub fn create_data_coin_input(coin: DataCoin, witness_index: u16) -> FuelInput {
FuelInput::data_coin_signed(
coin.utxo_id,
coin.owner.into(),
coin.amount,
coin.asset_id,
TxPointer::default(),
witness_index,
coin.data,
)
}

pub fn create_coin_message_input(message: Message, witness_index: u16) -> FuelInput {
if message.data.is_empty() {
FuelInput::message_coin_signed(
Expand All @@ -1442,21 +1471,34 @@ pub fn create_coin_message_input(message: Message, witness_index: u16) -> FuelIn
}
}

pub fn create_coin_predicate(
asset_id: AssetId,
coin: Coin,
pub fn create_coin_predicate(coin: Coin, code: Vec<u8>, predicate_data: Vec<u8>) -> FuelInput {
FuelInput::coin_predicate(
coin.utxo_id,
coin.owner.into(),
coin.amount,
coin.asset_id,
TxPointer::default(),
0u64,
code,
predicate_data,
)
}

pub fn create_data_coin_predicate(
coin: DataCoin,
code: Vec<u8>,
predicate_data: Vec<u8>,
) -> FuelInput {
FuelInput::coin_predicate(
FuelInput::data_coin_predicate(
coin.utxo_id,
coin.owner.into(),
coin.amount,
asset_id,
coin.asset_id,
TxPointer::default(),
0u64,
code,
predicate_data,
coin.data,
)
}

Expand Down
Loading
Loading