From 192f16382837fadbc625d8689e366f21b54eeda5 Mon Sep 17 00:00:00 2001 From: Cameron Carstens Date: Thu, 12 Dec 2024 07:26:49 +0000 Subject: [PATCH] Update `output_asset_to()` and `output_asset_id()` to handle `Output::Variable` (#6781) ## Description Allows for fetching of the `AssetId` and the to `Address` from an `Output::Variable`. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: K1-R1 <77465250+K1-R1@users.noreply.github.com> Co-authored-by: IGI-111 --- sway-lib-std/src/outputs.sw | 11 ++ test/src/sdk-harness/Forc.lock | 2 +- test/src/sdk-harness/Forc.toml | 2 +- .../test_artifacts/tx_contract/src/main.sw | 3 +- .../tx_output_change_contract/src/main.sw | 13 -- .../Forc.toml | 2 +- .../tx_output_contract/src/main.sw | 35 ++++++ .../tx_output_predicate/src/main.sw | 2 +- .../tx_type_predicate/src/main.sw | 2 +- .../tx_witness_predicate/src/main.sw | 11 +- .../test_projects/tx_fields/mod.rs | 115 +++++++++++++----- 11 files changed, 147 insertions(+), 51 deletions(-) delete mode 100644 test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw rename test/src/sdk-harness/test_artifacts/{tx_output_change_contract => tx_output_contract}/Forc.toml (81%) create mode 100644 test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw diff --git a/sway-lib-std/src/outputs.sw b/sway-lib-std/src/outputs.sw index 69ccac1b23b..527a94de2fc 100644 --- a/sway-lib-std/src/outputs.sw +++ b/sway-lib-std/src/outputs.sw @@ -28,6 +28,9 @@ pub const GTF_OUTPUT_COIN_ASSET_ID = 0x303; // pub const GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID = 0x307; // pub const GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT = 0x308; +const OUTPUT_VARIABLE_ASSET_ID_OFFSET = 48; +const OUTPUT_VARIABLE_TO_OFFSET = 8; + /// The output type for a transaction. pub enum Output { /// A coin output. @@ -228,6 +231,10 @@ pub fn output_asset_id(index: u64) -> Option { match output_type(index) { Some(Output::Coin) => Some(AssetId::from(__gtf::(index, GTF_OUTPUT_COIN_ASSET_ID))), Some(Output::Change) => Some(AssetId::from(__gtf::(index, GTF_OUTPUT_COIN_ASSET_ID))), + Some(Output::Variable) => { + let ptr = output_pointer(index).unwrap(); + Some(AssetId::from(ptr.add_uint_offset(OUTPUT_VARIABLE_ASSET_ID_OFFSET).read::())) + }, _ => None, } } @@ -260,6 +267,10 @@ pub fn output_asset_to(index: u64) -> Option
{ match output_type(index) { Some(Output::Coin) => Some(__gtf::
(index, GTF_OUTPUT_COIN_TO)), Some(Output::Change) => Some(__gtf::
(index, GTF_OUTPUT_COIN_TO)), + Some(Output::Variable) => { + let ptr = output_pointer(index).unwrap(); + Some(Address::from(ptr.add_uint_offset(OUTPUT_VARIABLE_TO_OFFSET).read::())) + }, _ => None, } } diff --git a/test/src/sdk-harness/Forc.lock b/test/src/sdk-harness/Forc.lock index 6db035f3459..ee70e29936a 100644 --- a/test/src/sdk-harness/Forc.lock +++ b/test/src/sdk-harness/Forc.lock @@ -420,7 +420,7 @@ source = "member" dependencies = ["std"] [[package]] -name = "tx_output_change_contract" +name = "tx_output_contract" source = "member" dependencies = ["std"] diff --git a/test/src/sdk-harness/Forc.toml b/test/src/sdk-harness/Forc.toml index e6b081c63d0..2f4dd422291 100644 --- a/test/src/sdk-harness/Forc.toml +++ b/test/src/sdk-harness/Forc.toml @@ -78,7 +78,7 @@ members = [ "test_artifacts/storage_vec/svec_u64", "test_artifacts/tx_contract", "test_artifacts/tx_input_count_predicate", - "test_artifacts/tx_output_change_contract", + "test_artifacts/tx_output_contract", "test_artifacts/tx_output_contract_creation_predicate", "test_artifacts/tx_output_count_predicate", "test_artifacts/tx_output_predicate", diff --git a/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw index ed80700620c..bce0730c604 100644 --- a/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_contract/src/main.sw @@ -135,7 +135,8 @@ impl TxContractTest for Contract { let mut iter = 0; while iter < expected_data_bytes.len() { - if data.get(iter).unwrap() != expected_data_bytes.get(iter).unwrap() { + if data.get(iter).unwrap() != expected_data_bytes.get(iter).unwrap() + { return false } iter += 1; diff --git a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw deleted file mode 100644 index 32aeeb5e7c9..00000000000 --- a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/src/main.sw +++ /dev/null @@ -1,13 +0,0 @@ -contract; - -use std::asset::transfer; - -abi TxOutputChangeContract { - fn send_assets(to: Address, asset: AssetId, amount: u64); -} - -impl TxOutputChangeContract for Contract { - fn send_assets(to: Address, asset: AssetId, amount: u64) { - transfer(Identity::Address(to), asset, amount); - } -} diff --git a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml b/test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml similarity index 81% rename from test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml rename to test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml index 5a053f78b77..824dbc61360 100644 --- a/test/src/sdk-harness/test_artifacts/tx_output_change_contract/Forc.toml +++ b/test/src/sdk-harness/test_artifacts/tx_output_contract/Forc.toml @@ -2,7 +2,7 @@ authors = ["Fuel Labs "] entry = "main.sw" license = "Apache-2.0" -name = "tx_output_change_contract" +name = "tx_output_contract" [dependencies] std = { path = "../../../../../sway-lib-std" } diff --git a/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw new file mode 100644 index 00000000000..3682db6ea9b --- /dev/null +++ b/test/src/sdk-harness/test_artifacts/tx_output_contract/src/main.sw @@ -0,0 +1,35 @@ +contract; + +use std::asset::transfer; +use std::outputs::*; + +abi TxOutputContract { + fn send_assets_change(to: Address, asset: AssetId, amount: u64); + fn send_assets_variable(to: Address, asset: AssetId, index: u64) -> (Address, AssetId, u64); +} + +impl TxOutputContract for Contract { + fn send_assets_change(to: Address, asset: AssetId, amount: u64) { + transfer(Identity::Address(to), asset, amount); + } + + fn send_assets_variable(to: Address, asset: AssetId, index: u64) -> (Address, AssetId, u64) { + transfer(Identity::Address(to), asset, 1); + + get_variable_tx_params(index) + } +} + +fn get_variable_tx_params(index: u64) -> (Address, AssetId, u64) { + let tx_asset_id = output_asset_id(index); + let tx_to = output_asset_to(index); + let tx_amount = output_amount(index); + + let tx_output_type = output_type(index); + assert(tx_output_type.is_some() && tx_output_type.unwrap() == Output::Variable); + ( + tx_to.unwrap_or(Address::zero()), + tx_asset_id.unwrap_or(AssetId::zero()), + tx_amount.unwrap_or(0), + ) +} diff --git a/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw index e639bd11833..6f6495c9a42 100644 --- a/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_output_predicate/src/main.sw @@ -1,6 +1,6 @@ predicate; -use std::outputs::{output_asset_id, output_asset_to, output_type, Output}; +use std::outputs::{Output, output_asset_id, output_asset_to, output_type}; fn main(index: u64, asset_id: b256, to: b256, expected_type: Output) -> bool { let tx_asset_id = output_asset_id(index); diff --git a/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw index 59bd4f10714..44938b3c833 100644 --- a/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_type_predicate/src/main.sw @@ -1,6 +1,6 @@ predicate; -use std::tx::{tx_type, Transaction}; +use std::tx::{Transaction, tx_type}; fn main(expected_type: Transaction) -> bool { tx_type() == expected_type diff --git a/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw b/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw index adb0ea9dbc9..294051eb0cb 100644 --- a/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/tx_witness_predicate/src/main.sw @@ -1,8 +1,13 @@ predicate; -use std::tx::{tx_witnesses_count, tx_witness_data_length, tx_witness_data}; +use std::tx::{tx_witness_data, tx_witness_data_length, tx_witnesses_count}; -fn main(index: u64, expected_count: u64, expected_length: u64, expected_data: [u8; 64]) -> bool { +fn main( + index: u64, + expected_count: u64, + expected_length: u64, + expected_data: [u8; 64], +) -> bool { let count: u64 = tx_witnesses_count(); let length: Option = tx_witness_data_length(index); let data: Option<[u8; 64]> = tx_witness_data(index); @@ -11,7 +16,7 @@ fn main(index: u64, expected_count: u64, expected_length: u64, expected_data: [u assert(length.is_some() && length.unwrap() == expected_length); assert(data.is_some()); - let data = data.unwrap(); + let data = data.unwrap(); let mut iter = 0; while iter < 64 { assert(data[iter] == expected_data[iter]); diff --git a/test/src/sdk-harness/test_projects/tx_fields/mod.rs b/test/src/sdk-harness/test_projects/tx_fields/mod.rs index 0ac934999c7..f7e7308737d 100644 --- a/test/src/sdk-harness/test_projects/tx_fields/mod.rs +++ b/test/src/sdk-harness/test_projects/tx_fields/mod.rs @@ -14,8 +14,8 @@ const MESSAGE_DATA: [u8; 3] = [1u8, 2u8, 3u8]; const TX_CONTRACT_BYTECODE_PATH: &str = "test_artifacts/tx_contract/out/release/tx_contract.bin"; const TX_OUTPUT_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_predicate/out/release/tx_output_predicate.bin"; -const TX_OUTPUT_CHANGE_CONTRACT_BYTECODE_PATH: &str = - "test_artifacts/tx_output_change_contract/out/release/tx_output_change_contract.bin"; +const TX_OUTPUT_CONTRACT_BYTECODE_PATH: &str = + "test_artifacts/tx_output_contract/out/release/tx_output_contract.bin"; const TX_FIELDS_PREDICATE_BYTECODE_PATH: &str = "test_projects/tx_fields/out/release/tx_fields.bin"; const TX_CONTRACT_CREATION_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_contract_creation_predicate/out/release/tx_output_contract_creation_predicate.bin"; @@ -28,8 +28,8 @@ const TX_INPUT_COUNT_PREDICATE_BYTECODE_PATH: &str = const TX_OUTPUT_COUNT_PREDICATE_BYTECODE_PATH: &str = "test_artifacts/tx_output_count_predicate/out/release/tx_output_count_predicate.bin"; -use crate::tx_fields::Transaction as SwayTransaction; use crate::tx_fields::Output as SwayOutput; +use crate::tx_fields::Transaction as SwayTransaction; abigen!( Contract( @@ -37,8 +37,8 @@ abigen!( abi = "test_artifacts/tx_contract/out/release/tx_contract-abi.json", ), Contract( - name = "TxOutputChangeContract", - abi = "test_artifacts/tx_output_change_contract/out/release/tx_output_change_contract-abi.json", + name = "TxOutputContract", + abi = "test_artifacts/tx_output_contract/out/release/tx_output_contract-abi.json", ), Predicate( name = "TestPredicate", @@ -172,7 +172,10 @@ async fn generate_predicate_inputs( (predicate_code, predicate_input, predicate_message) } -async fn setup_output_predicate(index: u64, expected_output_type: SwayOutput) -> (WalletUnlocked, WalletUnlocked, Predicate, AssetId, AssetId) { +async fn setup_output_predicate( + index: u64, + expected_output_type: SwayOutput, +) -> (WalletUnlocked, WalletUnlocked, Predicate, AssetId, AssetId) { let asset_id1 = AssetId::default(); let asset_id2 = AssetId::new([2u8; 32]); let wallets_config = WalletsConfig::new_multiple_assets( @@ -201,7 +204,12 @@ async fn setup_output_predicate(index: u64, expected_output_type: SwayOutput) -> let wallet2 = wallets.pop().unwrap(); let predicate_data = TestOutputPredicateEncoder::default() - .encode_data(index, Bits256([0u8; 32]), Bits256(*wallet1.address().hash()), expected_output_type) + .encode_data( + index, + Bits256([0u8; 32]), + Bits256(*wallet1.address().hash()), + expected_output_type, + ) .unwrap(); let predicate = Predicate::load_from(TX_OUTPUT_PREDICATE_BYTECODE_PATH) @@ -1547,7 +1555,8 @@ mod outputs { #[tokio::test] async fn can_get_tx_output_details() { - let (wallet, _, predicate, asset_id, _) = setup_output_predicate(0, SwayOutput::Coin).await; + let (wallet, _, predicate, asset_id, _) = + setup_output_predicate(0, SwayOutput::Coin).await; let balance = predicate.get_asset_balance(&asset_id).await.unwrap(); @@ -1678,32 +1687,39 @@ mod outputs { assert_eq!(predicate_balance, 0); } } - + #[tokio::test] async fn can_get_tx_output_change_details() { // Prepare predicate - let (wallet, _, predicate, asset_id, _) = setup_output_predicate(2, SwayOutput::Change).await; + let (wallet, _, predicate, asset_id, _) = + setup_output_predicate(2, SwayOutput::Change).await; let provider = wallet.try_provider().unwrap().clone(); let balance = predicate.get_asset_balance(&asset_id).await.unwrap(); // Deploy contract - let contract_id = Contract::load_from(TX_OUTPUT_CHANGE_CONTRACT_BYTECODE_PATH, LoadConfiguration::default()) - .unwrap() - .deploy(&wallet, TxPolicies::default()) - .await - .unwrap(); - - let instance = TxOutputChangeContract::new(contract_id.clone(), wallet.clone()); + let contract_id = Contract::load_from( + TX_OUTPUT_CONTRACT_BYTECODE_PATH, + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let instance = TxOutputContract::new(contract_id.clone(), wallet.clone()); // Send tokens to the contract let _ = wallet .force_transfer_to_contract(&contract_id, 10, asset_id, TxPolicies::default()) - .await - .unwrap(); + .await + .unwrap(); // Build transaction - let call_handler = instance.methods().send_assets(wallet.clone().address(), asset_id, 10); + let call_handler = + instance + .methods() + .send_assets_change(wallet.clone().address(), asset_id, 10); let mut tb = call_handler.transaction_builder().await.unwrap(); // Inputs for predicate @@ -1714,20 +1730,21 @@ mod outputs { .unwrap(); // Outputs for predicate - let predicate_output = wallet.get_asset_outputs_for_amount( - &wallet.address(), - asset_id, - transfer_amount, - ); + let predicate_output = + wallet.get_asset_outputs_for_amount(&wallet.address(), asset_id, transfer_amount); // Append the inputs and outputs to the transaction tb.inputs.push(predicate_input.get(0).unwrap().clone()); tb.outputs.push(predicate_output.get(0).unwrap().clone()); - tb.outputs.push(SdkOutput::Change{to: wallet.address().into(), amount: 0, asset_id}); + tb.outputs.push(SdkOutput::Change { + to: wallet.address().into(), + amount: 0, + asset_id, + }); wallet.adjust_for_fee(&mut tb, 0).await.unwrap(); tb.add_signer(wallet.clone()).unwrap(); - + let tx = tb.build(provider.clone()).await.unwrap(); let _tx_id = provider.send_transaction(tx).await.unwrap(); @@ -1735,6 +1752,44 @@ mod outputs { let new_balance = predicate.get_asset_balance(&asset_id).await.unwrap(); assert!(balance - transfer_amount == new_balance); } + + #[tokio::test] + async fn can_get_tx_output_variable_details() { + // Prepare wallet + let (wallet, _, _, asset_id, _) = setup_output_predicate(1, SwayOutput::Variable).await; + + // Deploy contract + let contract_id = Contract::load_from( + TX_OUTPUT_CONTRACT_BYTECODE_PATH, + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet, TxPolicies::default()) + .await + .unwrap(); + + let instance = TxOutputContract::new(contract_id.clone(), wallet.clone()); + + // Send tokens to the contract + let _ = wallet + .force_transfer_to_contract(&contract_id, 10, asset_id, TxPolicies::default()) + .await + .unwrap(); + + // Run transaction with variable output + let (tx_to, tx_asset_id, tx_amount) = instance + .methods() + .send_assets_variable(wallet.clone().address(), asset_id, 2) + .with_variable_output_policy(VariableOutputPolicy::Exactly(1)) + .call() + .await + .unwrap() + .value; + + assert_eq!(tx_to, wallet.clone().address().into()); + assert_eq!(tx_asset_id, asset_id); + assert_eq!(tx_amount, 1); + } } mod revert { @@ -1743,7 +1798,8 @@ mod outputs { #[tokio::test] #[should_panic] async fn fails_output_predicate_when_incorrect_asset() { - let (wallet1, _, predicate, _, asset_id2) = setup_output_predicate(0, SwayOutput::Coin).await; + let (wallet1, _, predicate, _, asset_id2) = + setup_output_predicate(0, SwayOutput::Coin).await; let transfer_amount = 10; predicate @@ -1760,7 +1816,8 @@ mod outputs { #[tokio::test] #[should_panic] async fn fails_output_predicate_when_incorrect_to() { - let (_, wallet2, predicate, asset_id1, _) = setup_output_predicate(0, SwayOutput::Coin).await; + let (_, wallet2, predicate, asset_id1, _) = + setup_output_predicate(0, SwayOutput::Coin).await; let transfer_amount = 10; predicate