From 97cbef72df4f95211cccf6eaf32d714c74f654ec Mon Sep 17 00:00:00 2001 From: Mayukha Vadari Date: Thu, 24 Sep 2026 03:37:48 +0530 Subject: [PATCH] Let a contract emit transactions from its own pseudo-account checkSign rejects every pseudo-account when BatchV1_1 is enabled, but emitted transactions need BatchV1_1 to skip the signature check. So no contract could emit anything. Allow a contract pseudo-account on inner transactions, and make emitBuiltTxn and emitTxn reject a transaction whose Account is not the running contract's account. Without that check, a contract could emit an unsigned transaction from any account. --- src/libxrpl/tx/Transactor.cpp | 7 ++++++- src/libxrpl/tx/wasm/ContractHostFuncImpl.cpp | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 920a97f3e2..5b428bed66 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -929,11 +929,16 @@ Transactor::checkSign( if ((view.rules().enabled(featureLendingProtocol) || view.rules().enabled(featureBatchV1_1) || view.rules().enabled(fixCleanup3_3_0)) && - isPseudoAccount(sle)) + isPseudoAccount(sle) && !(parentBatchId && sle->isFieldPresent(sfContractID))) { // Pseudo-accounts can't sign transactions. This check is gated on a // few different amendments so that it takes effect as soon as any of // them is activated. + // + // A contract's pseudo-account is the exception for inner transactions: + // the contract emits them, and the emit host functions allow only the + // running contract's own account. A Batch can't use this, because its + // BatchSigners are checked without a parentBatchId. return tefBAD_AUTH; } } diff --git a/src/libxrpl/tx/wasm/ContractHostFuncImpl.cpp b/src/libxrpl/tx/wasm/ContractHostFuncImpl.cpp index f528e0e435..3eb2375601 100644 --- a/src/libxrpl/tx/wasm/ContractHostFuncImpl.cpp +++ b/src/libxrpl/tx/wasm/ContractHostFuncImpl.cpp @@ -973,6 +973,11 @@ ContractHostFunctionsImpl::emitBuiltTxn(std::uint32_t const& index) return std::unexpected(HostFunctionError::SubmitTxnFailure); } + // Inner transactions skip the signature check, so a contract may emit + // only from its own account. + if (stx->getAccountID(sfAccount) != contractCtx.result.contractAccount) + return std::unexpected(HostFunctionError::SubmitTxnFailure); + // Use a persistent emit view that is seeded with the // transactor's pending state changes (balances, consumed // sequence, etc.) so that each emitted transaction validates @@ -1040,6 +1045,10 @@ ContractHostFunctionsImpl::emitTxn(std::shared_ptr const& stxPtr) return std::unexpected(HostFunctionError::SubmitTxnFailure); } + // See emitBuiltTxn: a contract may emit only from its own account. + if (txPtr->getAccountID(sfAccount) != contractCtx.result.contractAccount) + return std::unexpected(HostFunctionError::SubmitTxnFailure); + // Use a persistent emit view seeded with the transactor's // pending state, and do a full apply() for each emission // (see emitBuiltTxn for detailed rationale).