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);