diff --git a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs index c6e30b1fa..d552bbd83 100644 --- a/cmd/crates/soroban-test/tests/it/integration/hello_world.rs +++ b/cmd/crates/soroban-test/tests/it/integration/hello_world.rs @@ -133,8 +133,8 @@ async fn invoke_contract() { invoke_auth(sandbox, id, &addr); invoke_auth_with_identity(sandbox, id, "test", &addr); invoke_auth_with_identity(sandbox, id, "testone", &addr_1); + invoke_auth_with_non_source_identity(sandbox, id, "test", "testone", &addr_1); invoke_auth_with_different_test_account_fail(sandbox, id, &addr_1).await; - // invoke_auth_with_different_test_account(sandbox, id); contract_data_read_failure(sandbox, id); invoke_with_seed(sandbox, id, &seed_phrase).await; invoke_with_sk(sandbox, id, &secret_key).await; @@ -225,6 +225,30 @@ fn invoke_auth_with_identity(sandbox: &TestEnv, id: &str, key: &str, addr: &str) .success(); } +fn invoke_auth_with_non_source_identity( + sandbox: &TestEnv, + id: &str, + source: &str, + key: &str, + addr: &str, +) { + sandbox + .new_assert_cmd("contract") + .arg("invoke") + .arg("--source") + .arg(source) + .arg("--id") + .arg(id) + .arg("--") + .arg("auth") + .arg("--addr") + .arg(key) + .arg("--world=world") + .assert() + .stdout(format!("\"{addr}\"\n")) + .success(); +} + async fn invoke_auth_with_different_test_account_fail(sandbox: &TestEnv, id: &str, addr: &str) { let res = sandbox .invoke_with_test(&[ diff --git a/cmd/soroban-cli/src/assembled.rs b/cmd/soroban-cli/src/assembled.rs index c3c9a4087..1681b8609 100644 --- a/cmd/soroban-cli/src/assembled.rs +++ b/cmd/soroban-cli/src/assembled.rs @@ -1,10 +1,9 @@ use sha2::{Digest, Sha256}; use stellar_xdr::curr::{ - self as xdr, Hash, InvokeHostFunctionOp, LedgerFootprint, Limits, Operation, OperationBody, - ReadXdr, SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanResources, - SorobanTransactionData, Transaction, TransactionEnvelope, TransactionExt, - TransactionSignaturePayload, TransactionSignaturePayloadTaggedTransaction, - TransactionV1Envelope, VecM, WriteXdr, + self as xdr, Hash, LedgerFootprint, Limits, OperationBody, ReadXdr, SorobanAuthorizationEntry, + SorobanAuthorizedFunction, SorobanResources, SorobanTransactionData, Transaction, + TransactionEnvelope, TransactionExt, TransactionSignaturePayload, + TransactionSignaturePayloadTaggedTransaction, TransactionV1Envelope, VecM, WriteXdr, }; use soroban_rpc::{Error, LogEvents, LogResources, ResourceConfig, SimulateTransactionResponse}; @@ -148,11 +147,6 @@ impl Assembled { Ok(()) } - #[must_use] - pub fn requires_auth(&self) -> bool { - requires_auth(&self.txn).is_some() - } - #[must_use] pub fn requires_fee_bump(&self) -> bool { self.fee_bump_fee.is_some() @@ -287,21 +281,6 @@ fn assemble( }) } -fn requires_auth(txn: &Transaction) -> Option { - let [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] = txn.operations.as_slice() - else { - return None; - }; - matches!( - auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) - .then(move || op.clone()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/cmd/soroban-cli/src/config/mod.rs b/cmd/soroban-cli/src/config/mod.rs index 7eb79fff4..4f6ce6491 100644 --- a/cmd/soroban-cli/src/config/mod.rs +++ b/cmd/soroban-cli/src/config/mod.rs @@ -146,13 +146,11 @@ impl Args { signers: &[Signer], ) -> Result, Error> { let network = self.get_network()?; - let source_signer = self.source_signer().await?; let client = network.rpc_client()?; let latest_ledger = client.get_latest_ledger().await?.sequence; let seq_num = latest_ledger + 60; // ~ 5 min Ok(signer::sign_soroban_authorizations( tx, - &source_signer, signers, seq_num, &network.network_passphrase, diff --git a/cmd/soroban-cli/src/signer/mod.rs b/cmd/soroban-cli/src/signer/mod.rs index 7482fcf94..de348229c 100644 --- a/cmd/soroban-cli/src/signer/mod.rs +++ b/cmd/soroban-cli/src/signer/mod.rs @@ -2,11 +2,10 @@ use crate::{ utils::fee_bump_transaction_hash, xdr::{ self, AccountId, DecoratedSignature, FeeBumpTransactionEnvelope, Hash, HashIdPreimage, - HashIdPreimageSorobanAuthorization, InvokeHostFunctionOp, Limits, Operation, OperationBody, - PublicKey, ScAddress, ScMap, ScSymbol, ScVal, Signature, SignatureHint, - SorobanAddressCredentials, SorobanAuthorizationEntry, SorobanAuthorizedFunction, - SorobanCredentials, Transaction, TransactionEnvelope, TransactionV1Envelope, Uint256, VecM, - WriteXdr, + HashIdPreimageSorobanAuthorization, Limits, Operation, OperationBody, PublicKey, ScAddress, + ScMap, ScSymbol, ScVal, Signature, SignatureHint, SorobanAddressCredentials, + SorobanAuthorizationEntry, SorobanCredentials, Transaction, TransactionEnvelope, + TransactionV1Envelope, Uint256, VecM, WriteXdr, }, }; use ed25519_dalek::{ed25519::signature::Signer as _, Signature as Ed25519Signature}; @@ -50,39 +49,24 @@ pub enum Error { Decode(#[from] stellar_strkey::DecodeError), } -fn requires_auth(txn: &Transaction) -> Option { - let [op @ Operation { - body: OperationBody::InvokeHostFunction(InvokeHostFunctionOp { auth, .. }), - .. - }] = txn.operations.as_slice() - else { - return None; - }; - matches!( - auth.first().map(|x| &x.root_invocation.function), - Some(&SorobanAuthorizedFunction::ContractFn(_)) - ) - .then(move || op.clone()) -} - -// Use the given source_key and signers, to sign all SorobanAuthorizationEntry's in the given -// transaction. If unable to sign, return an error. +/// Sign all SorobanAuthorizationEntry's in the transaction with the given signers. Returns a new +/// transaction with the signatures added to each SorobanAuthorizationEntry. +/// +/// If no SorobanAuthorizationEntry's need signing (including if none exist), return Ok(None). +/// +/// If a SorobanAuthorizationEntry needs signing, but a signature cannot be produced for it, +/// return an Error pub fn sign_soroban_authorizations( raw: &Transaction, - source_signer: &Signer, signers: &[Signer], signature_expiration_ledger: u32, network_passphrase: &str, ) -> Result, Error> { - let mut tx = raw.clone(); - let Some(mut op) = requires_auth(&tx) else { - return Ok(None); - }; - - let Operation { - body: OperationBody::InvokeHostFunction(ref mut body), + // Check if we have exactly one operation and it's InvokeHostFunction + let [op @ Operation { + body: OperationBody::InvokeHostFunction(body), .. - } = op + }] = raw.operations.as_slice() else { return Ok(None); }; @@ -127,10 +111,6 @@ pub fn sign_soroban_authorizations( } } - if needle == &source_signer.get_public_key()?.0 { - signer = Some(source_signer); - } - match signer { Some(signer) => { let signed_entry = sign_soroban_authorization_entry( @@ -152,9 +132,21 @@ pub fn sign_soroban_authorizations( } } - body.auth = signed_auths.try_into()?; - tx.operations = vec![op].try_into()?; - Ok(Some(tx)) + // No signatures were made, return None to indicate no change to the transaction + if signed_auths.is_empty() { + return Ok(None); + } + + // Build updated transaction with signed auth entries + let mut updated_op = op.clone(); + if let OperationBody::InvokeHostFunction(ref mut updated_body) = updated_op.body { + let mut tx = raw.clone(); + updated_body.auth = signed_auths.try_into()?; + tx.operations = vec![updated_op].try_into()?; + Ok(Some(tx)) + } else { + Ok(None) + } } fn sign_soroban_authorization_entry(