Skip to content

Commit 14fd076

Browse files
committed
Implement CAP-0085 create-contract admission validation
Thread ledgerVersion through TransactionFrameBase::validateHostFn and accept CONTRACT_EXECUTABLE_EXTERNAL_REF for FROM_ADDRESS CREATE_CONTRACT / CREATE_CONTRACT_V2 starting at EXTERNAL_EXECUTABLE_REF_PROTOCOL_VERSION (protocol 28), matching CAP-0085 ("InvokeHostFunctionOp update") and the p28 host (rs-soroban-env#1703). Removes the SPIKE TODO that had left external-ref creates rejected core-side. FROM_ASSET still requires STELLAR_ASSET; non-next behavior unchanged. Adds a gated TransactionQueue section covering both host-fn variants (external-ref accepted at p28 / rejected below it, FROM_ASSET+external-ref rejected).
1 parent b8749b8 commit 14fd076

9 files changed

Lines changed: 81 additions & 29 deletions

src/herder/TransactionQueue.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ TransactionQueue::canAdd(
526526
txSOROBAN_INVALID, diagnosticEvents.finalize());
527527
}
528528

529-
if (!tx->validateHostFn())
529+
if (!tx->validateHostFn(ledgerVersion))
530530
{
531531
return AddResult(TransactionQueue::AddResultCode::ADD_STATUS_ERROR, *tx,
532532
txSOROBAN_INVALID, diagnosticEvents.finalize());

src/herder/test/TransactionQueueTests.cpp

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "transactions/TransactionBridge.h"
2525
#include "transactions/TransactionUtils.h"
2626
#include "transactions/test/SorobanTxTestUtils.h"
27+
#include "util/ProtocolVersion.h"
2728
#include "util/Timer.h"
2829
#include "util/numeric128.h"
2930
#include "xdr/Stellar-transaction.h"
@@ -1264,7 +1265,7 @@ TEST_CASE("Soroban tx filtering", "[soroban][transactionqueue]")
12641265
TransactionQueue::AddResultCode::ADD_STATUS_ERROR);
12651266
}
12661267

1267-
auto runInvalidCreateContractTest =
1268+
auto makeCreateContractTx =
12681269
[&](HostFunctionType hostFnType, ContractIDPreimageType preimageType,
12691270
ContractExecutableType executableType) {
12701271
Operation createOp;
@@ -1290,6 +1291,14 @@ TEST_CASE("Soroban tx filtering", "[soroban][transactionqueue]")
12901291
{
12911292
exec.wasm_hash() = sha256(wasm.data);
12921293
}
1294+
#ifdef CAP_0085_EXECUTABLE_REF
1295+
else if (executableType == CONTRACT_EXECUTABLE_EXTERNAL_REF)
1296+
{
1297+
exec.external_ref().executable_owner =
1298+
makeContractAddress(sha256("owner"));
1299+
exec.external_ref().tag = "tag";
1300+
}
1301+
#endif
12931302
};
12941303

12951304
if (hostFnType == HOST_FUNCTION_TYPE_CREATE_CONTRACT)
@@ -1305,9 +1314,16 @@ TEST_CASE("Soroban tx filtering", "[soroban][transactionqueue]")
13051314
createHF.createContractV2().executable);
13061315
}
13071316

1308-
auto tx = sorobanTransactionFrameFromOpsWithTotalFee(
1317+
return sorobanTransactionFrameFromOpsWithTotalFee(
13091318
app->getNetworkID(), a1, {createOp}, {}, resources,
13101319
uploadResourceFee + 100, uploadResourceFee);
1320+
};
1321+
1322+
auto runInvalidCreateContractTest =
1323+
[&](HostFunctionType hostFnType, ContractIDPreimageType preimageType,
1324+
ContractExecutableType executableType) {
1325+
auto tx =
1326+
makeCreateContractTx(hostFnType, preimageType, executableType);
13111327
auto feeBumpTx =
13121328
feeBump(*app, feeBumper, tx, uploadResourceFee + 200);
13131329

@@ -1336,6 +1352,35 @@ TEST_CASE("Soroban tx filtering", "[soroban][transactionqueue]")
13361352
CONTRACT_ID_PREIMAGE_FROM_ADDRESS,
13371353
CONTRACT_EXECUTABLE_STELLAR_ASSET);
13381354
}
1355+
1356+
#ifdef CAP_0085_EXECUTABLE_REF
1357+
SECTION("CAP-0085 external ref executable")
1358+
{
1359+
uint32_t const gate =
1360+
static_cast<uint32_t>(EXTERNAL_EXECUTABLE_REF_PROTOCOL_VERSION);
1361+
1362+
// FROM_ADDRESS + external ref is accepted starting at the CAP-0085
1363+
// protocol version and rejected just below it, for both host-fn
1364+
// variants.
1365+
for (auto hostFnType : {HOST_FUNCTION_TYPE_CREATE_CONTRACT,
1366+
HOST_FUNCTION_TYPE_CREATE_CONTRACT_V2})
1367+
{
1368+
auto tx = makeCreateContractTx(hostFnType,
1369+
CONTRACT_ID_PREIMAGE_FROM_ADDRESS,
1370+
CONTRACT_EXECUTABLE_EXTERNAL_REF);
1371+
REQUIRE(tx->validateHostFn(gate));
1372+
REQUIRE_FALSE(tx->validateHostFn(gate - 1));
1373+
}
1374+
1375+
// FROM_ASSET + external ref stays rejected.
1376+
runInvalidCreateContractTest(HOST_FUNCTION_TYPE_CREATE_CONTRACT,
1377+
CONTRACT_ID_PREIMAGE_FROM_ASSET,
1378+
CONTRACT_EXECUTABLE_EXTERNAL_REF);
1379+
runInvalidCreateContractTest(HOST_FUNCTION_TYPE_CREATE_CONTRACT_V2,
1380+
CONTRACT_ID_PREIMAGE_FROM_ASSET,
1381+
CONTRACT_EXECUTABLE_EXTERNAL_REF);
1382+
}
1383+
#endif
13391384
}
13401385

13411386
TEST_CASE("TransactionQueue Key Filtering", "[soroban][transactionqueue]")

src/transactions/FeeBumpTransactionFrame.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,9 +544,9 @@ FeeBumpTransactionFrame::validateSorobanMemo() const
544544
}
545545

546546
bool
547-
FeeBumpTransactionFrame::validateHostFn() const
547+
FeeBumpTransactionFrame::validateHostFn(uint32_t ledgerVersion) const
548548
{
549-
return mInnerTx->validateHostFn();
549+
return mInnerTx->validateHostFn(ledgerVersion);
550550
}
551551

552552
int64_t

src/transactions/FeeBumpTransactionFrame.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class FeeBumpTransactionFrame : public TransactionFrameBase
149149
bool validateAccountFilterForFlooding(
150150
std::set<AccountID> const& filteredAccounts) const override;
151151
bool validateSorobanMemo() const override;
152-
bool validateHostFn() const override;
152+
bool validateHostFn(uint32_t ledgerVersion) const override;
153153

154154
int64_t getFullFee() const override;
155155
int64_t getInclusionFee() const override;

src/transactions/TransactionFrame.cpp

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ TransactionFrame::validateAccountFilterForFlooding(
354354
}
355355

356356
bool
357-
TransactionFrame::validateHostFn() const
357+
TransactionFrame::validateHostFn(uint32_t ledgerVersion) const
358358
{
359359
if (!isSoroban())
360360
{
@@ -376,28 +376,35 @@ TransactionFrame::validateHostFn() const
376376
auto const& hostFn = op.body.invokeHostFunctionOp().hostFunction;
377377

378378
auto validateCreateContract =
379-
[](ContractIDPreimage const& preimage,
380-
ContractExecutable const& executable) -> bool {
379+
[ledgerVersion](ContractIDPreimage const& preimage,
380+
ContractExecutable const& executable) -> bool {
381381
if (preimage.type() == CONTRACT_ID_PREIMAGE_FROM_ASSET &&
382382
executable.type() != CONTRACT_EXECUTABLE_STELLAR_ASSET)
383383
{
384384
return false;
385385
}
386-
// TODO(CAP-0085, SPIKE): once the p28 host acceptance logic
387-
// (rs-soroban-env#1703) and the CAP-0085 spec are cross-checked, relax
388-
// this to also accept CONTRACT_EXECUTABLE_EXTERNAL_REF for FROM_ADDRESS
389-
// creates when
390-
// protocolVersionStartsFrom(ledgerVersion,
391-
// EXTERNAL_EXECUTABLE_REF_PROTOCOL_VERSION).
392-
// That requires threading ledgerVersion (from the current LCL header)
393-
// through the virtual validateHostFn() interface
394-
// (TransactionFrameBase.h + the FeeBump/TestFrame overrides + the
395-
// TransactionQueue caller). Deferred for this SPIKE: external-ref creates
396-
// stay rejected core-side, which keeps existing behavior/tests intact.
397-
if (preimage.type() == CONTRACT_ID_PREIMAGE_FROM_ADDRESS &&
398-
executable.type() != CONTRACT_EXECUTABLE_WASM)
399-
{
400-
return false;
386+
if (preimage.type() == CONTRACT_ID_PREIMAGE_FROM_ADDRESS)
387+
{
388+
bool validExecutable =
389+
executable.type() == CONTRACT_EXECUTABLE_WASM;
390+
#ifdef CAP_0085_EXECUTABLE_REF
391+
// CAP-0085: FROM_ADDRESS creates may also target an external
392+
// executable reference starting at the CAP-0085 protocol version.
393+
// CREATE_CONTRACT / CREATE_CONTRACT_V2 resolve
394+
// CONTRACT_EXECUTABLE_EXTERNAL_REF with the same semantics as
395+
// create_external_ref_contract (CAP-0085 spec, InvokeHostFunctionOp
396+
// update). The host enforces reference validity (rs-soroban-env
397+
// frame.rs); this admission check only gates the executable type.
398+
validExecutable =
399+
validExecutable ||
400+
(protocolVersionStartsFrom(
401+
ledgerVersion, EXTERNAL_EXECUTABLE_REF_PROTOCOL_VERSION) &&
402+
executable.type() == CONTRACT_EXECUTABLE_EXTERNAL_REF);
403+
#endif
404+
if (!validExecutable)
405+
{
406+
return false;
407+
}
401408
}
402409
return true;
403410
};

src/transactions/TransactionFrame.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class TransactionFrame : public TransactionFrameBase
224224
bool validateAccountFilterForFlooding(
225225
std::set<AccountID> const& filteredAccounts) const override;
226226
bool validateSorobanMemo() const override;
227-
bool validateHostFn() const override;
227+
bool validateHostFn(uint32_t ledgerVersion) const override;
228228

229229
int64_t getFullFee() const override;
230230
int64_t getInclusionFee() const override;

src/transactions/TransactionFrameBase.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ class TransactionFrameBase
230230
virtual bool validateAccountFilterForFlooding(
231231
std::set<AccountID> const& filteredAccounts) const = 0;
232232
virtual bool validateSorobanMemo() const = 0;
233-
virtual bool validateHostFn() const = 0;
233+
virtual bool validateHostFn(uint32_t ledgerVersion) const = 0;
234234

235235
// Returns the total fee of this transaction, including the 'flat',
236236
// non-market part.

src/transactions/test/TransactionTestFrame.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,9 @@ TransactionTestFrame::validateSorobanMemo() const
219219
}
220220

221221
bool
222-
TransactionTestFrame::validateHostFn() const
222+
TransactionTestFrame::validateHostFn(uint32_t ledgerVersion) const
223223
{
224-
return mTransactionFrame->validateHostFn();
224+
return mTransactionFrame->validateHostFn(ledgerVersion);
225225
}
226226

227227
int64_t

src/transactions/test/TransactionTestFrame.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class TransactionTestFrame : public TransactionFrameBase
108108
bool validateAccountFilterForFlooding(
109109
std::set<AccountID> const& filteredAccounts) const override;
110110
bool validateSorobanMemo() const override;
111-
bool validateHostFn() const override;
111+
bool validateHostFn(uint32_t ledgerVersion) const override;
112112

113113
// Returns the total fee of this transaction, including the 'flat',
114114
// non-market part.

0 commit comments

Comments
 (0)