88import static com .hedera .services .bdd .spec .assertions .ContractFnResultAsserts .resultWith ;
99import static com .hedera .services .bdd .spec .assertions .TransactionRecordAsserts .recordWith ;
1010import static com .hedera .services .bdd .spec .assertions .TransferListAsserts .including ;
11+ import static com .hedera .services .bdd .spec .dsl .entities .SpecTokenKey .SUPPLY_KEY ;
1112import static com .hedera .services .bdd .spec .keys .KeyShape .DELEGATE_CONTRACT ;
1213import static com .hedera .services .bdd .spec .keys .KeyShape .sigs ;
1314import static com .hedera .services .bdd .spec .keys .SigControl .ON ;
3334import static com .hedera .services .bdd .spec .transactions .token .CustomFeeSpecs .fractionalFee ;
3435import static com .hedera .services .bdd .spec .transactions .token .CustomFeeSpecs .royaltyFeeWithFallback ;
3536import static com .hedera .services .bdd .spec .transactions .token .TokenMovement .moving ;
37+ import static com .hedera .services .bdd .spec .transactions .token .TokenMovement .movingHbar ;
38+ import static com .hedera .services .bdd .spec .transactions .token .TokenMovement .movingUnique ;
3639import static com .hedera .services .bdd .spec .utilops .CustomSpecAssert .allRunFor ;
3740import static com .hedera .services .bdd .spec .utilops .UtilVerbs .accountAmount ;
41+ import static com .hedera .services .bdd .spec .utilops .UtilVerbs .accountAmountAlias ;
42+ import static com .hedera .services .bdd .spec .utilops .UtilVerbs .assertCloseEnough ;
43+ import static com .hedera .services .bdd .spec .utilops .UtilVerbs .assertionsHold ;
3844import static com .hedera .services .bdd .spec .utilops .UtilVerbs .childRecordsCheck ;
3945import static com .hedera .services .bdd .spec .utilops .UtilVerbs .newKeyNamed ;
4046import static com .hedera .services .bdd .spec .utilops .UtilVerbs .nftTransfer ;
47+ import static com .hedera .services .bdd .spec .utilops .UtilVerbs .nftTransferToAlias ;
4148import static com .hedera .services .bdd .spec .utilops .UtilVerbs .overriding ;
4249import static com .hedera .services .bdd .spec .utilops .UtilVerbs .sourcing ;
50+ import static com .hedera .services .bdd .spec .utilops .UtilVerbs .sourcingContextual ;
4351import static com .hedera .services .bdd .spec .utilops .UtilVerbs .tokenTransferList ;
4452import static com .hedera .services .bdd .spec .utilops .UtilVerbs .tokenTransferLists ;
4553import static com .hedera .services .bdd .spec .utilops .UtilVerbs .transferList ;
5058import static com .hedera .services .bdd .suites .HapiSuite .ONE_HBAR ;
5159import static com .hedera .services .bdd .suites .HapiSuite .ONE_HUNDRED_HBARS ;
5260import static com .hedera .services .bdd .suites .contract .Utils .asHexedSolidityAddress ;
61+ import static com .hedera .services .bdd .suites .utils .MiscEETUtils .genRandomBytes ;
5362import static com .hedera .services .bdd .suites .utils .MiscEETUtils .metadata ;
5463import static com .hedera .services .bdd .suites .utils .contracts .precompile .HTSPrecompileResult .htsPrecompileResult ;
5564import static com .hederahashgraph .api .proto .java .ResponseCodeEnum .AMOUNT_EXCEEDS_ALLOWANCE ;
6372import com .esaulpaugh .headlong .abi .Tuple ;
6473import com .hedera .node .app .hapi .utils .ByteStringUtils ;
6574import com .hedera .services .bdd .junit .HapiTest ;
75+ import com .hedera .services .bdd .junit .HapiTestLifecycle ;
6676import com .hedera .services .bdd .junit .LeakyHapiTest ;
6777import com .hedera .services .bdd .spec .assertions .ContractInfoAsserts ;
6878import com .hedera .services .bdd .spec .assertions .NonFungibleTransfers ;
6979import com .hedera .services .bdd .spec .assertions .SomeFungibleTransfers ;
80+ import com .hedera .services .bdd .spec .dsl .annotations .Contract ;
81+ import com .hedera .services .bdd .spec .dsl .annotations .FungibleToken ;
82+ import com .hedera .services .bdd .spec .dsl .annotations .NonFungibleToken ;
83+ import com .hedera .services .bdd .spec .dsl .entities .SpecContract ;
84+ import com .hedera .services .bdd .spec .dsl .entities .SpecFungibleToken ;
85+ import com .hedera .services .bdd .spec .dsl .entities .SpecNonFungibleToken ;
7086import com .hedera .services .bdd .spec .keys .KeyShape ;
7187import com .hedera .services .bdd .spec .transactions .token .TokenMovement ;
7288import com .hederahashgraph .api .proto .java .TokenSupplyType ;
8096import org .junit .jupiter .api .Tag ;
8197
8298@ Tag (SMART_CONTRACT )
99+ @ HapiTestLifecycle
83100public class AtomicCryptoTransferHTSSuite {
84101 private static final long GAS_FOR_AUTO_ASSOCIATING_CALLS = 2_000_000 ;
85102 private static final Tuple [] EMPTY_TUPLE_ARRAY = new Tuple [] {};
@@ -104,6 +121,106 @@ public class AtomicCryptoTransferHTSSuite {
104121
105122 public static final String SECP_256K1_SOURCE_KEY = "secp256k1Alias" ;
106123
124+ @ HapiTest
125+ final Stream <DynamicTest > hollowAccountCreationFeesAsExpected (
126+ @ Contract (creationGas = 2_000_000 , contract = "AtomicCryptoTransfer" , maxAutoAssociations = 2 )
127+ SpecContract contract ,
128+ @ FungibleToken SpecFungibleToken fungibleToken ,
129+ @ NonFungibleToken (
130+ keys = {SUPPLY_KEY },
131+ numPreMints = 1 )
132+ SpecNonFungibleToken nonFungibleToken ) {
133+ final AtomicLong hbarAutoCreationGas = new AtomicLong ();
134+ final AtomicLong ftAutoCreationGas = new AtomicLong ();
135+ final AtomicLong nftAutoCreationGas = new AtomicLong ();
136+ return hapiTest (
137+ contract .getInfo (),
138+ fungibleToken .getInfo (),
139+ nonFungibleToken .getInfo (),
140+ // Give the contract assets to use in creating hollow accounts
141+ cryptoTransfer (
142+ movingHbar (10 * ONE_HUNDRED_HBARS ).between (GENESIS , contract .name ()),
143+ moving (1 , fungibleToken .name ())
144+ .between (fungibleToken .treasury ().name (), contract .name ()),
145+ movingUnique (nonFungibleToken .name (), 1 )
146+ .between (nonFungibleToken .treasury ().name (), contract .name ()))
147+ .signedBy (
148+ GENESIS ,
149+ fungibleToken .treasury ().name (),
150+ nonFungibleToken .treasury ().name ()),
151+ // First auto-create a hollow account with HBAR
152+ sourcingContextual (spec -> contractCall (
153+ contract .name (),
154+ "transferMultipleTokens" ,
155+ transferList ()
156+ .withAccountAmounts (
157+ accountAmount (
158+ spec .registry ().getAccountID (contract .name ()), -1L , false ),
159+ accountAmountAlias (genRandomBytes (20 ), +1L ))
160+ .build (),
161+ EMPTY_TUPLE_ARRAY )
162+ .payingWith (GENESIS )
163+ .via ("hbarAutoCreation" )
164+ .gas (2_000_000L )),
165+ getTxnRecord ("hbarAutoCreation" )
166+ .exposingTo (r -> hbarAutoCreationGas .set (
167+ r .getContractCallResult ().getGasUsed ())),
168+ // Then auto-create a hollow account with a fungible token
169+ sourcingContextual (spec -> contractCall (
170+ contract .name (),
171+ "transferMultipleTokens" ,
172+ transferList ()
173+ .withAccountAmounts (EMPTY_TUPLE_ARRAY )
174+ .build (),
175+ wrapIntoTupleArray (tokenTransferList ()
176+ .forToken (spec .registry ().getTokenID (fungibleToken .name ()))
177+ .withAccountAmounts (
178+ accountAmount (
179+ spec .registry ().getAccountID (contract .name ()), -1L , false ),
180+ accountAmountAlias (genRandomBytes (20 ), +1L ))
181+ .build ()))
182+ .payingWith (GENESIS )
183+ .via ("ftAutoCreation" )
184+ .gas (2_000_000L )),
185+ getTxnRecord ("ftAutoCreation" )
186+ .exposingTo (r ->
187+ ftAutoCreationGas .set (r .getContractCallResult ().getGasUsed ())),
188+ // And finally auto-create a hollow account with a non-fungible token
189+ sourcingContextual (spec -> contractCall (
190+ contract .name (),
191+ "transferMultipleTokens" ,
192+ transferList ()
193+ .withAccountAmounts (EMPTY_TUPLE_ARRAY )
194+ .build (),
195+ wrapIntoTupleArray (tokenTransferList ()
196+ .forToken (spec .registry ().getTokenID (nonFungibleToken .name ()))
197+ .withNftTransfers (nftTransferToAlias (
198+ spec .registry ().getAccountID (contract .name ()), genRandomBytes (20 ), 1L ))
199+ .build ()))
200+ .payingWith (GENESIS )
201+ .via ("nftAutoCreation" )
202+ .gas (2_000_000L )),
203+ getTxnRecord ("nftAutoCreation" )
204+ .exposingTo (r ->
205+ nftAutoCreationGas .set (r .getContractCallResult ().getGasUsed ())),
206+ assertionsHold ((spec , opLog ) -> {
207+ // The HTS auto-creations should be almost exactly the same
208+ assertCloseEnough (
209+ 1.0 ,
210+ 1.0 * ftAutoCreationGas .get () / nftAutoCreationGas .get (),
211+ 1.0 ,
212+ "ratio of FT to NFT auto-creation gas" ,
213+ "via IHederaTokenService" );
214+ // The HBAR auto-creation should be ~1/2 of them, since it doesn't require an auto-association
215+ assertCloseEnough (
216+ 0.5 ,
217+ 1.0 * hbarAutoCreationGas .get () / ftAutoCreationGas .get (),
218+ 5.0 ,
219+ "ratio of HBAR to FT auto-creation gas" ,
220+ "via IHederaTokenService" );
221+ }));
222+ }
223+
107224 @ HapiTest
108225 final Stream <DynamicTest > cryptoTransferForHbarOnly () {
109226 final var cryptoTransferTxn = "cryptoTransferTxn" ;
0 commit comments