diff --git a/sway-lib-std/src/auth.sw b/sway-lib-std/src/auth.sw index a775cc3ed88..a18675a3027 100644 --- a/sway-lib-std/src/auth.sw +++ b/sway-lib-std/src/auth.sw @@ -6,8 +6,16 @@ use ::contract_id::ContractId; use ::identity::Identity; use ::option::Option::{self, *}; use ::result::Result::{self, *}; -use ::inputs::{Input, input_coin_owner, input_count, input_message_recipient, input_type,}; +use ::inputs::{ + Input, + input_coin_owner, + input_count, + input_message_recipient, + input_message_sender, + input_type, +}; use ::revert::revert; +use ::vec::Vec; /// The error type used when an `Identity` cannot be determined. pub enum AuthError { @@ -192,6 +200,52 @@ pub fn caller_address() -> Result { } } +/// Get the owners of the inputs (of type `Input::Coin` or `Input::Message`) to a +/// `TransactionScript`. +/// +/// # Additional Information +/// +/// The list is not deduplicated, so there may be repeated addresses in the returned vector. +/// +/// # Returns +/// +/// * [Vec
] - The addresses of the owners of the inputs. +/// +/// # Examples +/// +/// ```sway +/// use std::auth::caller_addresses; +/// +/// fn foo(some_address: Address) { +/// let addresses = caller_addresses(); +/// +/// assert(addresses.get(0).unwrap() == some_address); +/// } +/// ``` +pub fn caller_addresses() -> Vec
{ + let inputs = input_count().as_u64(); + let mut addresses = Vec::new(); + let mut iter = 0; + + while iter < inputs { + // Call the corresponding function based on the input type. + let input_owner = match input_type(iter) { + Some(Input::Coin) => input_coin_owner(iter), + Some(Input::Message) => input_message_sender(iter), + _ => None, // If not Coin or Message, loop continues. + }; + + // If we successfully retrieved an owner address, add it to the vector. + if let Some(address) = input_owner { + addresses.push(address); + } + + iter += 1; + } + + addresses +} + /// Get the current predicate's address when called in an internal context. /// /// # Returns diff --git a/test/src/sdk-harness/test_artifacts/auth_testing_abi/src/main.sw b/test/src/sdk-harness/test_artifacts/auth_testing_abi/src/main.sw index e45ec9c5f97..ab48c3e4756 100644 --- a/test/src/sdk-harness/test_artifacts/auth_testing_abi/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/auth_testing_abi/src/main.sw @@ -4,4 +4,5 @@ abi AuthTesting { fn is_caller_external() -> bool; fn returns_msg_sender(expected_id: ContractId) -> bool; fn returns_msg_sender_address(expected_id: Address) -> bool; + fn returns_caller_addresses() -> Vec
; } diff --git a/test/src/sdk-harness/test_artifacts/auth_testing_contract/src/main.sw b/test/src/sdk-harness/test_artifacts/auth_testing_contract/src/main.sw index 4fd51ea5edc..ff145ae4d3d 100644 --- a/test/src/sdk-harness/test_artifacts/auth_testing_contract/src/main.sw +++ b/test/src/sdk-harness/test_artifacts/auth_testing_contract/src/main.sw @@ -43,4 +43,8 @@ impl AuthTesting for Contract { } ret } + + fn returns_caller_addresses() -> Vec
{ + caller_addresses() + } } diff --git a/test/src/sdk-harness/test_projects/auth/mod.rs b/test/src/sdk-harness/test_projects/auth/mod.rs index 45441e38582..2fefd28f8c8 100644 --- a/test/src/sdk-harness/test_projects/auth/mod.rs +++ b/test/src/sdk-harness/test_projects/auth/mod.rs @@ -135,6 +135,434 @@ async fn input_message_msg_sender_from_contract() { assert!(response.value); } +#[tokio::test] +async fn caller_addresses_from_messages() { + let mut wallet1 = WalletUnlocked::new_random(None); + let mut wallet2 = WalletUnlocked::new_random(None); + let mut wallet3 = WalletUnlocked::new_random(None); + let mut wallet4 = WalletUnlocked::new_random(None); + + // Setup message + let message_amount = 10; + let message1 = Message { + sender: wallet1.address().clone(), + recipient: wallet1.address().clone(), + nonce: 0.into(), + amount: message_amount, + data: vec![], + da_height: 0, + status: MessageStatus::Unspent, + }; + let message2 = Message { + sender: wallet2.address().clone(), + recipient: wallet2.address().clone(), + nonce: 1.into(), + amount: message_amount, + data: vec![], + da_height: 0, + status: MessageStatus::Unspent, + }; + let message3 = Message { + sender: wallet3.address().clone(), + recipient: wallet3.address().clone(), + nonce: 2.into(), + amount: message_amount, + data: vec![], + da_height: 0, + status: MessageStatus::Unspent, + }; + let mut message_vec: Vec = Vec::new(); + message_vec.push(message1); + message_vec.push(message2); + message_vec.push(message3); + + // Setup Coin + let coin_amount = 10; + let coin = Coin { + owner: wallet4.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 0), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + + let mut node_config = NodeConfig::default(); + node_config.starting_gas_price = 0; + let provider = setup_test_provider(vec![coin], message_vec, Some(node_config), None) + .await + .unwrap(); + + wallet1.set_provider(provider.clone()); + wallet2.set_provider(provider.clone()); + wallet3.set_provider(provider.clone()); + + wallet4.set_provider(provider.clone()); + + let id_1 = Contract::load_from( + "test_artifacts/auth_testing_contract/out/release/auth_testing_contract.bin", + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet4, TxPolicies::default()) + .await + .unwrap(); + + let auth_instance = AuthContract::new(id_1.clone(), wallet4.clone()); + + let result = auth_instance + .methods() + .returns_caller_addresses() + .call() + .await + .unwrap(); + + assert_eq!(result.value, vec![Address::from(*wallet4.address().hash())]); + + // Start building transactions + let call_handler = auth_instance + .methods() + .returns_caller_addresses(); + let mut tb = call_handler.transaction_builder().await.unwrap(); + + // Inputs + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Message( + setup_single_message( + &wallet1.address().clone(), + &wallet1.address().clone(), + message_amount, + 0.into(), + vec![], + ) + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Message( + setup_single_message( + &wallet2.address().clone(), + &wallet2.address().clone(), + message_amount, + 1.into(), + vec![], + ) + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Message( + setup_single_message( + &wallet3.address().clone(), + &wallet3.address().clone(), + message_amount, + 2.into(), + vec![], + ) + ), + }); + + // Build transaction + tb.add_signer(wallet1.clone()).unwrap(); + tb.add_signer(wallet2.clone()).unwrap(); + tb.add_signer(wallet3.clone()).unwrap(); + + let provider = wallet1.provider().unwrap(); + let tx = tb.build(provider.clone()).await.unwrap(); + + // Send and verify + let tx_id = provider.send_transaction(tx).await.unwrap(); + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + let result = call_handler.get_response_from(tx_status).unwrap(); + + assert!(result.value.contains(&Address::from(wallet1.address().clone()))); + assert!(result.value.contains(&Address::from(wallet2.address().clone()))); + assert!(result.value.contains(&Address::from(wallet3.address().clone()))); +} + +#[tokio::test] +async fn caller_addresses_from_coins() { + let mut wallet1 = WalletUnlocked::new_random(None); + let mut wallet2 = WalletUnlocked::new_random(None); + let mut wallet3 = WalletUnlocked::new_random(None); + let mut wallet4 = WalletUnlocked::new_random(None); + + // Setup Coin + let coin_amount = 10; + let coin1 = Coin { + owner: wallet1.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 0), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + let coin2 = Coin { + owner: wallet2.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 1), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + let coin3 = Coin { + owner: wallet3.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 2), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + let coin4 = Coin { + owner: wallet4.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 3), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + + let mut coin_vec: Vec = Vec::new(); + coin_vec.push(coin1); + coin_vec.push(coin2); + coin_vec.push(coin3); + coin_vec.push(coin4); + + let mut node_config = NodeConfig::default(); + node_config.starting_gas_price = 0; + let provider = setup_test_provider(coin_vec, vec![], Some(node_config), None) + .await + .unwrap(); + + wallet1.set_provider(provider.clone()); + wallet2.set_provider(provider.clone()); + wallet3.set_provider(provider.clone()); + + wallet4.set_provider(provider.clone()); + + let id_1 = Contract::load_from( + "test_artifacts/auth_testing_contract/out/release/auth_testing_contract.bin", + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet4, TxPolicies::default()) + .await + .unwrap(); + + let auth_instance = AuthContract::new(id_1.clone(), wallet4.clone()); + + let result = auth_instance + .methods() + .returns_caller_addresses() + .call() + .await + .unwrap(); + + assert_eq!(result.value, vec![Address::from(*wallet4.address().hash())]); + + // Start building transactions + let call_handler = auth_instance + .methods() + .returns_caller_addresses(); + let mut tb = call_handler.transaction_builder().await.unwrap(); + + // Inputs + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Coin( + Coin { + owner: wallet1.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 0), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + } + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Coin( + Coin { + owner: wallet2.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 1), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + } + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Coin( + Coin { + owner: wallet3.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 2), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + } + ), + }); + + // Build transaction + tb.add_signer(wallet1.clone()).unwrap(); + tb.add_signer(wallet2.clone()).unwrap(); + tb.add_signer(wallet3.clone()).unwrap(); + + let provider = wallet1.provider().unwrap(); + let tx = tb.build(provider.clone()).await.unwrap(); + + // Send and verify + let tx_id = provider.send_transaction(tx).await.unwrap(); + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + let result = call_handler.get_response_from(tx_status).unwrap(); + + assert!(result.value.contains(&Address::from(wallet1.address().clone()))); + assert!(result.value.contains(&Address::from(wallet2.address().clone()))); + assert!(result.value.contains(&Address::from(wallet3.address().clone()))); +} + +#[tokio::test] +async fn caller_addresses_from_coins_and_messages() { + let mut wallet1 = WalletUnlocked::new_random(None); + let mut wallet2 = WalletUnlocked::new_random(None); + let mut wallet3 = WalletUnlocked::new_random(None); + let mut wallet4 = WalletUnlocked::new_random(None); + + let message_amount = 10; + let message1 = Message { + sender: wallet1.address().clone(), + recipient: wallet1.address().clone(), + nonce: 0.into(), + amount: message_amount, + data: vec![], + da_height: 0, + status: MessageStatus::Unspent, + }; + + // Setup Coin + let coin_amount = 10; + let coin2 = Coin { + owner: wallet2.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 1), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + let coin3 = Coin { + owner: wallet3.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 2), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + let coin4 = Coin { + owner: wallet4.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 3), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + }; + + let mut coin_vec: Vec = Vec::new(); + coin_vec.push(coin2); + coin_vec.push(coin3); + coin_vec.push(coin4); + + let mut node_config = NodeConfig::default(); + node_config.starting_gas_price = 0; + let provider = setup_test_provider(coin_vec, vec![message1], Some(node_config), None) + .await + .unwrap(); + + wallet1.set_provider(provider.clone()); + wallet2.set_provider(provider.clone()); + wallet3.set_provider(provider.clone()); + + wallet4.set_provider(provider.clone()); + + let id_1 = Contract::load_from( + "test_artifacts/auth_testing_contract/out/release/auth_testing_contract.bin", + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet4, TxPolicies::default()) + .await + .unwrap(); + + let auth_instance = AuthContract::new(id_1.clone(), wallet4.clone()); + + let result = auth_instance + .methods() + .returns_caller_addresses() + .call() + .await + .unwrap(); + + assert_eq!(result.value, vec![Address::from(*wallet4.address().hash())]); + + // Start building transactions + let call_handler = auth_instance + .methods() + .returns_caller_addresses(); + let mut tb = call_handler.transaction_builder().await.unwrap(); + + // Inputs + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Message( + setup_single_message( + &wallet1.address().clone(), + &wallet1.address().clone(), + message_amount, + 0.into(), + vec![], + ) + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Coin( + Coin { + owner: wallet2.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 1), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + } + ), + }); + tb.inputs_mut().push(Input::ResourceSigned { + resource: CoinType::Coin( + Coin { + owner: wallet3.address().clone(), + utxo_id: UtxoId::new(Bytes32::zeroed(), 2), + amount: coin_amount, + asset_id: AssetId::default(), + status: CoinStatus::Unspent, + block_created: Default::default(), + } + ), + }); + + // Build transaction + tb.add_signer(wallet1.clone()).unwrap(); + tb.add_signer(wallet2.clone()).unwrap(); + tb.add_signer(wallet3.clone()).unwrap(); + + let provider = wallet1.provider().unwrap(); + let tx = tb.build(provider.clone()).await.unwrap(); + + // Send and verify + let tx_id = provider.send_transaction(tx).await.unwrap(); + let tx_status = provider.tx_status(&tx_id).await.unwrap(); + let result = call_handler.get_response_from(tx_status).unwrap(); + + assert!(result.value.contains(&Address::from(wallet1.address().clone()))); + assert!(result.value.contains(&Address::from(wallet2.address().clone()))); + assert!(result.value.contains(&Address::from(wallet3.address().clone()))); +} + async fn get_contracts() -> ( AuthContract, ContractId, @@ -196,7 +624,7 @@ async fn can_get_predicate_address() { // Setup predicate. let hex_predicate_address: &str = - "0x0c07e9f6e71da8855fb65a38f299a993aeba71fd0eef6c1ac4c79beff09cd6f7"; + "0x5dcc82a88eebb07fb628db93d11ec38f085cbf36453a7135fea41b93cc44e118"; let predicate_address = Address::from_str(hex_predicate_address).expect("failed to create Address from string"); let predicate_bech32_address = Bech32Address::from(predicate_address); @@ -322,7 +750,7 @@ async fn when_incorrect_predicate_address_passed() { async fn can_get_predicate_address_in_message() { // Setup predicate address. let hex_predicate_address: &str = - "0x0c07e9f6e71da8855fb65a38f299a993aeba71fd0eef6c1ac4c79beff09cd6f7"; + "0x5dcc82a88eebb07fb628db93d11ec38f085cbf36453a7135fea41b93cc44e118"; let predicate_address = Address::from_str(hex_predicate_address).expect("failed to create Address from string"); let predicate_bech32_address = Bech32Address::from(predicate_address);