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.
This commit is contained in:
Mayukha Vadari
2026-09-24 03:37:48 +05:30
parent 670f1d5e6f
commit 97cbef72df
2 changed files with 15 additions and 1 deletions

View File

@@ -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;
}
}

View File

@@ -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<STTx const> 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).