Skip to content

Commit 599d85b

Browse files
authored
Fix core for CAP-0073 (#5153)
# Description Add test for CAP-0073 and allow trustline/account creation. <!--- Describe what this pull request does, which issue it's resolving (usually applicable for code changes). ---> # Checklist - [ ] Reviewed the [contributing](https://github.com/stellar/stellar-core/blob/master/CONTRIBUTING.md#submitting-changes) document - [ ] Rebased on top of master (no merge commits) - [ ] Ran `clang-format` v8.0.0 (via `make format` or the Visual Studio extension) - [ ] Compiles - [ ] Ran all tests - [ ] If change impacts performance, include supporting evidence per the [performance document](https://github.com/stellar/stellar-core/blob/master/performance-eval/performance-eval.md)
2 parents 4032574 + 356286b commit 599d85b

5 files changed

Lines changed: 156 additions & 7 deletions

File tree

src/transactions/InvokeHostFunctionOpFrame.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,8 @@ class InvokeHostFunctionApplyHelper : virtual LedgerAccessHelper
659659
}
660660

661661
// Check that each newly created ContractCode or ContractData entry also
662-
// creates an ttlEntry
662+
// creates a ttlEntry. Starting from protocol 26 (CAP-73), the Stellar
663+
// Asset Contract can also create classic entries (ACCOUNT, TRUSTLINE).
663664
for (auto const& key : createdKeys)
664665
{
665666
if (isSorobanEntry(key))
@@ -668,6 +669,13 @@ class InvokeHostFunctionApplyHelper : virtual LedgerAccessHelper
668669
releaseAssertOrThrow(createdKeys.find(ttlKey) !=
669670
createdKeys.end());
670671
}
672+
else if (protocolVersionStartsFrom(getLedgerVersion(),
673+
ProtocolVersion::V_26))
674+
{
675+
releaseAssertOrThrow(key.type() == TTL ||
676+
key.type() == ACCOUNT ||
677+
key.type() == TRUSTLINE);
678+
}
671679
else
672680
{
673681
releaseAssertOrThrow(key.type() == TTL);

src/transactions/test/InvokeHostFunctionTests.cpp

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,37 @@ TEST_CASE_VERSIONS("Trustline stellar asset contract",
325325
checkSponsorship(ltx, acc, 0, nullptr, 1, 2, 0, 1);
326326
checkSponsorship(ltx, sponsor, 0, nullptr, 0, 2, 1, 0);
327327
}
328+
329+
// Test CAP-73: trust() creates a trustline via the SAC
330+
if (protocolVersionStartsFrom(test.getLedgerVersion(),
331+
ProtocolVersion::V_26))
332+
{
333+
auto trustAcc = root.create(
334+
"trustAcc", app.getLedgerManager().getLastMinBalance(2));
335+
// Account has no trustline for IDR yet
336+
{
337+
LedgerTxn ltx(app.getLedgerTxnRoot());
338+
auto tlAsset = assetToTrustLineAsset(idr);
339+
REQUIRE(
340+
!ltx.load(trustlineKey(trustAcc.getPublicKey(), tlAsset)));
341+
}
342+
343+
REQUIRE(client.trust(trustAcc));
344+
345+
// Trustline should now exist with zero balance and max limit
346+
{
347+
LedgerTxn ltx(app.getLedgerTxnRoot());
348+
auto tlAsset = assetToTrustLineAsset(idr);
349+
auto tlEntry =
350+
ltx.load(trustlineKey(trustAcc.getPublicKey(), tlAsset));
351+
REQUIRE(tlEntry);
352+
REQUIRE(tlEntry.current().data.trustLine().balance == 0);
353+
REQUIRE(tlEntry.current().data.trustLine().limit == INT64_MAX);
354+
}
355+
356+
// Calling trust again should be a no-op (idempotent)
357+
REQUIRE(client.trust(trustAcc));
358+
}
328359
});
329360
}
330361

@@ -427,6 +458,84 @@ TEST_CASE("Native stellar asset contract",
427458
checkSponsorship(ltx, root.getPublicKey(), 0, nullptr, 0, 2, 2, 0);
428459
}
429460

461+
// Test CAP-73: XLM transfer to non-existent account creates the account
462+
if (protocolVersionStartsFrom(test.getLedgerVersion(),
463+
ProtocolVersion::V_26))
464+
{
465+
auto newAccKey = SecretKey::pseudoRandomForTesting();
466+
auto newAccPub = newAccKey.getPublicKey();
467+
auto newAccAddr = makeAccountAddress(newAccPub);
468+
469+
// Account should not exist yet
470+
REQUIRE(!doesAccountExist(app, newAccPub));
471+
472+
// Transfer 2 * base_reserve (minimum to create account) via SAC
473+
auto baseReserve =
474+
static_cast<int64_t>(app.getLedgerManager().getLastReserve());
475+
auto createAmount = 2 * baseReserve;
476+
477+
// Construct the footprint manually since the destination account
478+
// doesn't exist yet and client.transfer() would try to read its
479+
// balance.
480+
LedgerKey fromAccountKey(ACCOUNT);
481+
fromAccountKey.account().accountID = a1.getPublicKey();
482+
LedgerKey toAccountKey(ACCOUNT);
483+
toAccountKey.account().accountID = newAccPub;
484+
485+
auto spec = client.defaultSpec().setReadWriteFootprint(
486+
{fromAccountKey, toAccountKey});
487+
488+
SCVal fromVal(SCV_ADDRESS);
489+
fromVal.address() = makeAccountAddress(a1.getPublicKey());
490+
SCVal toVal(SCV_ADDRESS);
491+
toVal.address() = newAccAddr;
492+
493+
auto invocation =
494+
client.getContract()
495+
.prepareInvocation(
496+
"transfer", {fromVal, toVal, makeI128(createAmount)}, spec)
497+
.withAuthorizedTopCall();
498+
REQUIRE(invocation.invoke(&a1));
499+
500+
// Account should now exist with the correct balance
501+
REQUIRE(doesAccountExist(app, newAccPub));
502+
{
503+
LedgerTxn ltx(app.getLedgerTxnRoot());
504+
auto entry = stellar::loadAccount(ltx, newAccPub);
505+
REQUIRE(entry.current().data.account().balance == createAmount);
506+
}
507+
508+
// Calling trust on the native asset should fail
509+
REQUIRE(!client.trust(a2));
510+
511+
// Transferring below minimum should fail for a non-existent account
512+
auto newAccKey2 = SecretKey::pseudoRandomForTesting();
513+
auto newAccPub2 = newAccKey2.getPublicKey();
514+
auto newAccAddr2 = makeAccountAddress(newAccPub2);
515+
516+
REQUIRE(!doesAccountExist(app, newAccPub2));
517+
518+
LedgerKey toAccountKey2(ACCOUNT);
519+
toAccountKey2.account().accountID = newAccPub2;
520+
521+
auto specFail = client.defaultSpec().setReadWriteFootprint(
522+
{fromAccountKey, toAccountKey2});
523+
524+
SCVal toVal2(SCV_ADDRESS);
525+
toVal2.address() = newAccAddr2;
526+
527+
auto failInvocation =
528+
client.getContract()
529+
.prepareInvocation(
530+
"transfer", {fromVal, toVal2, makeI128(createAmount - 1)},
531+
specFail)
532+
.withAuthorizedTopCall();
533+
REQUIRE(!failInvocation.invoke(&a1));
534+
535+
// Account should still not exist
536+
REQUIRE(!doesAccountExist(app, newAccPub2));
537+
}
538+
430539
// Test batch_transfer with 5 destinations (protocol 23+)
431540
if (app.getConfig().LEDGER_PROTOCOL_VERSION >= 23)
432541
{

src/transactions/test/SorobanTxTestUtils.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,6 +1975,36 @@ AssetContractTestClient::clawback(TestAccount& admin, SCAddress const& fromAddr,
19751975
return success;
19761976
}
19771977

1978+
bool
1979+
AssetContractTestClient::trust(TestAccount& addr)
1980+
{
1981+
auto addrSC = makeAccountAddress(addr.getPublicKey());
1982+
1983+
SCVal addrVal(SCV_ADDRESS);
1984+
addrVal.address() = addrSC;
1985+
1986+
LedgerKey trustlineKey(TRUSTLINE);
1987+
trustlineKey.trustLine().accountID = addr.getPublicKey();
1988+
trustlineKey.trustLine().asset = assetToTrustLineAsset(mAsset);
1989+
1990+
LedgerKey accountKey(ACCOUNT);
1991+
accountKey.account().accountID = addr.getPublicKey();
1992+
1993+
auto spec = defaultSpec();
1994+
// If this is the native asset, we expect the tx to fail, but we still want
1995+
// to be able to test that failure, so make sure the tx that is built passes
1996+
// initial validation.
1997+
if (mAsset.type() != ASSET_TYPE_NATIVE)
1998+
{
1999+
spec = spec.setReadWriteFootprint({trustlineKey, accountKey})
2000+
.extendReadOnlyFootprint({makeIssuerKey(mAsset)});
2001+
}
2002+
2003+
auto invocation = mContract.prepareInvocation("trust", {addrVal}, spec)
2004+
.withAuthorizedTopCall();
2005+
return invocation.invoke(&addr);
2006+
}
2007+
19782008
ContractStorageTestClient::ContractStorageTestClient(
19792009
SorobanTest& test, int64_t additionalRefundableFee)
19802010
: mContract(test.deployWasmContract(

src/transactions/test/SorobanTxTestUtils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ class AssetContractTestClient
406406
bool burn(TestAccount& from, int64_t amount);
407407
bool clawback(TestAccount& admin, SCAddress const& fromAddr,
408408
int64_t amount);
409+
bool trust(TestAccount& addr);
409410
TestContract const& getContract() const;
410411
std::optional<ContractEvent> lastEvent() const;
411412

test-tx-meta-baseline-next/InvokeHostFunctionTests.json

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@
5151
[
5252
"+cR3oq2qY0I=",
5353
"v/UMJBmXfpU=",
54-
"LQG7YSJ+Z2c=",
55-
"RUhJJwe1y8c=",
56-
"yDLYOPhABhU=",
57-
"f9dntA2zYFY=",
58-
"6R0Ftqkuayg="
54+
"c5hMI0Elm+M=",
55+
"Hy46FpEBO/4=",
56+
"Y47MsGv0nS8=",
57+
"VW84T3660GI=",
58+
"15WZmVivTk4="
5959
],
6060
"Soroban authorization" :
6161
[
@@ -1416,7 +1416,8 @@
14161416
"ei4T6aiso3E=",
14171417
"yYwUqope2ZU=",
14181418
"Xl79rsoQeGo=",
1419-
"wHwgNBvEIfA="
1419+
"wHwgNBvEIfA=",
1420+
"kskE4Ud1al0="
14201421
],
14211422
"Vm instantiation tightening" : [ "MR6BJ3xmn2c=" ],
14221423
"archival meta|protocol version 20" :

0 commit comments

Comments
 (0)