Skip to content

Commit 403c4b2

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 b6234b4 commit 403c4b2

10 files changed

Lines changed: 136 additions & 83 deletions

Cargo.lock

Lines changed: 20 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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: 82 additions & 37 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,50 +1265,65 @@ TEST_CASE("Soroban tx filtering", "[soroban][transactionqueue]")
12641265
TransactionQueue::AddResultCode::ADD_STATUS_ERROR);
12651266
}
12661267

1267-
auto runInvalidCreateContractTest =
1268-
[&](HostFunctionType hostFnType, ContractIDPreimageType preimageType,
1269-
ContractExecutableType executableType) {
1270-
Operation createOp;
1271-
createOp.body.type(INVOKE_HOST_FUNCTION);
1272-
auto& createHF = createOp.body.invokeHostFunctionOp().hostFunction;
1273-
createHF.type(hostFnType);
1274-
1275-
auto setPreimageAndExecutable = [&](ContractIDPreimage& preimage,
1276-
ContractExecutable& exec) {
1277-
preimage.type(preimageType);
1278-
if (preimageType == CONTRACT_ID_PREIMAGE_FROM_ASSET)
1279-
{
1280-
preimage.fromAsset() = makeNativeAsset();
1281-
}
1282-
else
1283-
{
1284-
preimage.fromAddress().address =
1285-
makeAccountAddress(a1.getPublicKey());
1286-
preimage.fromAddress().salt = sha256("salt");
1287-
}
1288-
exec.type(executableType);
1289-
if (executableType == CONTRACT_EXECUTABLE_WASM)
1290-
{
1291-
exec.wasm_hash() = sha256(wasm.data);
1292-
}
1293-
};
1268+
auto makeCreateContractTx = [&](HostFunctionType hostFnType,
1269+
ContractIDPreimageType preimageType,
1270+
ContractExecutableType executableType) {
1271+
Operation createOp;
1272+
createOp.body.type(INVOKE_HOST_FUNCTION);
1273+
auto& createHF = createOp.body.invokeHostFunctionOp().hostFunction;
1274+
createHF.type(hostFnType);
12941275

1295-
if (hostFnType == HOST_FUNCTION_TYPE_CREATE_CONTRACT)
1276+
auto setPreimageAndExecutable = [&](ContractIDPreimage& preimage,
1277+
ContractExecutable& exec) {
1278+
preimage.type(preimageType);
1279+
if (preimageType == CONTRACT_ID_PREIMAGE_FROM_ASSET)
12961280
{
1297-
setPreimageAndExecutable(
1298-
createHF.createContract().contractIDPreimage,
1299-
createHF.createContract().executable);
1281+
preimage.fromAsset() = makeNativeAsset();
13001282
}
13011283
else
13021284
{
1303-
setPreimageAndExecutable(
1304-
createHF.createContractV2().contractIDPreimage,
1305-
createHF.createContractV2().executable);
1285+
preimage.fromAddress().address =
1286+
makeAccountAddress(a1.getPublicKey());
1287+
preimage.fromAddress().salt = sha256("salt");
13061288
}
1289+
exec.type(executableType);
1290+
if (executableType == CONTRACT_EXECUTABLE_WASM)
1291+
{
1292+
exec.wasm_hash() = sha256(wasm.data);
1293+
}
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
1302+
};
13071303

1308-
auto tx = sorobanTransactionFrameFromOpsWithTotalFee(
1309-
app->getNetworkID(), a1, {createOp}, {}, resources,
1310-
uploadResourceFee + 100, uploadResourceFee);
1304+
if (hostFnType == HOST_FUNCTION_TYPE_CREATE_CONTRACT)
1305+
{
1306+
setPreimageAndExecutable(
1307+
createHF.createContract().contractIDPreimage,
1308+
createHF.createContract().executable);
1309+
}
1310+
else
1311+
{
1312+
setPreimageAndExecutable(
1313+
createHF.createContractV2().contractIDPreimage,
1314+
createHF.createContractV2().executable);
1315+
}
1316+
1317+
return sorobanTransactionFrameFromOpsWithTotalFee(
1318+
app->getNetworkID(), a1, {createOp}, {}, resources,
1319+
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)