@@ -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,81 @@ 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+ // Transferring below minimum should fail for a non-existent account
509+ auto newAccKey2 = SecretKey::pseudoRandomForTesting ();
510+ auto newAccPub2 = newAccKey2.getPublicKey ();
511+ auto newAccAddr2 = makeAccountAddress (newAccPub2);
512+
513+ REQUIRE (!doesAccountExist (app, newAccPub2));
514+
515+ LedgerKey toAccountKey2 (ACCOUNT );
516+ toAccountKey2.account ().accountID = newAccPub2;
517+
518+ auto specFail = client.defaultSpec ().setReadWriteFootprint (
519+ {fromAccountKey, toAccountKey2});
520+
521+ SCVal toVal2 (SCV_ADDRESS );
522+ toVal2.address () = newAccAddr2;
523+
524+ auto failInvocation =
525+ client.getContract ()
526+ .prepareInvocation (
527+ " transfer" , {fromVal, toVal2, makeI128 (createAmount - 1 )},
528+ specFail)
529+ .withAuthorizedTopCall ();
530+ REQUIRE (!failInvocation.invoke (&a1));
531+
532+ // Account should still not exist
533+ REQUIRE (!doesAccountExist (app, newAccPub2));
534+ }
535+
430536 // Test batch_transfer with 5 destinations (protocol 23+)
431537 if (app.getConfig ().LEDGER_PROTOCOL_VERSION >= 23 )
432538 {
0 commit comments