Skip to content

Commit

Permalink
test: Token bulk operations and fees tests (#16892)
Browse files Browse the repository at this point in the history
Signed-off-by: Lev Povolotsky <[email protected]>
Signed-off-by: Kim Rader <[email protected]>
Signed-off-by: Eva <[email protected]>
Signed-off-by: Evdokia-Georgieva <[email protected]>
Co-authored-by: Lev Povolotsky <[email protected]>
Co-authored-by: Kim Rader <[email protected]>
Co-authored-by: Michael Tinker <[email protected]>
  • Loading branch information
4 people authored Jan 7, 2025
1 parent 602f625 commit 59be979
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2024-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hedera.services.bdd.suites.fees;

import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate;
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate;
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS;
import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON;
import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE;

import com.hedera.services.bdd.spec.SpecOperation;
import com.hederahashgraph.api.proto.java.TokenSupplyType;
import java.util.ArrayList;
import java.util.List;

/**
* Base class for tests to validate bulk operations fees
*/
public class BulkOperationsBase {
protected static final String OWNER = "owner";
protected static final String RECEIVER = "receiver";
protected static final String ASSOCIATE_ACCOUNT = "associateAccount";

protected static final String NFT_TOKEN = "nftToken";
protected static final String NFT_BURN_ONE_TOKEN = "nftBurnOneToken";
protected static final String NFT_BURN_TOKEN = "nftBurnToken";
protected static final String FT_TOKEN = "ftToken";

/**
* Create tokens and accounts
*
* @return array of operations
*/
protected static SpecOperation[] createTokensAndAccounts() {
var supplyKey = "supplyKey";
final var t = new ArrayList<SpecOperation>(List.of(
newKeyNamed(supplyKey),
cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).key(supplyKey),
tokenCreate(NFT_TOKEN)
.treasury(OWNER)
.tokenType(NON_FUNGIBLE_UNIQUE)
.supplyKey(supplyKey)
.supplyType(TokenSupplyType.INFINITE)
.initialSupply(0),
tokenCreate(FT_TOKEN)
.treasury(OWNER)
.tokenType(FUNGIBLE_COMMON)
.supplyKey(supplyKey)
.initialSupply(1000L),
tokenCreate(NFT_BURN_TOKEN)
.treasury(OWNER)
.tokenType(NON_FUNGIBLE_UNIQUE)
.supplyKey(supplyKey)
.supplyType(TokenSupplyType.INFINITE)
.initialSupply(0),
tokenCreate(NFT_BURN_ONE_TOKEN)
.treasury(OWNER)
.tokenType(NON_FUNGIBLE_UNIQUE)
.supplyKey(supplyKey)
.supplyType(TokenSupplyType.INFINITE)
.initialSupply(0)));

return t.toArray(new SpecOperation[0]);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 Hedera Hashgraph, LLC
* Copyright (C) 2024-2025 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -84,11 +84,13 @@
import com.hederahashgraph.api.proto.java.TokenSupplyType;
import com.hederahashgraph.api.proto.java.TokenType;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;

@Tag(TOKEN)
Expand Down Expand Up @@ -122,6 +124,8 @@ public class TokenServiceFeesSuite {
private static final String FUNGIBLE_TOKEN = "fungibleToken";
private static final String RECEIVER_WITH_0_AUTO_ASSOCIATIONS = "receiverWith0AutoAssociations";

private static final String TOKEN_UPDATE_METADATA = "tokenUpdateMetadata";

private static final double EXPECTED_NFT_WIPE_PRICE_USD = 0.001;
private static final double EXPECTED_FREEZE_PRICE_USD = 0.001;
private static final double EXPECTED_UNFREEZE_PRICE_USD = 0.001;
Expand All @@ -132,6 +136,10 @@ public class TokenServiceFeesSuite {
private static final double EXPECTED_FUNGIBLE_MINT_PRICE_USD = 0.001;
private static final double EXPECTED_FUNGIBLE_REJECT_PRICE_USD = 0.001;
private static final double EXPECTED_NFT_REJECT_PRICE_USD = 0.001;

private static final double EXPECTED_ASSOCIATE_TOKEN_PRICE = 0.05;
private static final double EXPECTED_NFT_UPDATE_PRICE = 0.001;

private static final String OWNER = "owner";

@HapiTest
Expand Down Expand Up @@ -905,6 +913,123 @@ final Stream<DynamicTest> tokenUpdateNftsFeeChargedAsExpected() {
validateChargedUsd("nftUpdateTxn", expectedTokenUpdateNfts));
}

// verify bulk operations base fees
@Nested
@DisplayName("Token Bulk Operations - without custom fees")
class BulkTokenOperationsWithoutCustomFeesTest extends BulkOperationsBase {

@HapiTest
final Stream<DynamicTest> mintOneNftTokenWithoutCustomFees() {
return mintBulkNftAndValidateFees(1);
}

@HapiTest
final Stream<DynamicTest> mintFiveBulkNftTokenWithoutCustomFees() {
return mintBulkNftAndValidateFees(5);
}

@HapiTest
final Stream<DynamicTest> mintTenBulkNftTokensWithoutCustomFees() {
return mintBulkNftAndValidateFees(10);
}

@HapiTest
final Stream<DynamicTest> associateOneFtTokenWithoutCustomFees() {
return associateBulkTokensAndValidateFees(List.of(FT_TOKEN));
}

@HapiTest
final Stream<DynamicTest> associateBulkFtTokensWithoutCustomFees() {
return associateBulkTokensAndValidateFees(List.of(FT_TOKEN, NFT_TOKEN, NFT_BURN_TOKEN, NFT_BURN_ONE_TOKEN));
}

@HapiTest
final Stream<DynamicTest> updateOneNftTokenWithoutCustomFees() {
return updateBulkNftTokensAndValidateFees(10, Arrays.asList(1L));
}

@HapiTest
final Stream<DynamicTest> updateFiveBulkNftTokensWithoutCustomFees() {
return updateBulkNftTokensAndValidateFees(10, Arrays.asList(1L, 2L, 3L, 4L, 5L));
}

@HapiTest
final Stream<DynamicTest> updateTenBulkNftTokensWithoutCustomFees() {
return updateBulkNftTokensAndValidateFees(10, Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L));
}

// define reusable methods
private Stream<DynamicTest> mintBulkNftAndValidateFees(final int rangeAmount) {
final var supplyKey = "supplyKey";
return hapiTest(
newKeyNamed(supplyKey),
cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).key(supplyKey),
tokenCreate(NFT_TOKEN)
.treasury(OWNER)
.tokenType(NON_FUNGIBLE_UNIQUE)
.supplyKey(supplyKey)
.supplyType(TokenSupplyType.INFINITE)
.initialSupply(0),
mintToken(
NFT_TOKEN,
IntStream.range(0, rangeAmount)
.mapToObj(a -> ByteString.copyFromUtf8(String.valueOf(a)))
.toList())
.payingWith(OWNER)
.signedBy(supplyKey)
.blankMemo()
.via("mintTxn"),
validateChargedUsdWithin(
"mintTxn", EXPECTED_NFT_MINT_PRICE_USD * rangeAmount, ALLOWED_DIFFERENCE_PERCENTAGE));
}

private Stream<DynamicTest> associateBulkTokensAndValidateFees(final List<String> tokens) {
final var supplyKey = "supplyKey";
return hapiTest(flattened(
createTokensAndAccounts(),
newKeyNamed(supplyKey),
cryptoCreate(ASSOCIATE_ACCOUNT).balance(ONE_HUNDRED_HBARS).key(supplyKey),
tokenAssociate(ASSOCIATE_ACCOUNT, tokens)
.payingWith(ASSOCIATE_ACCOUNT)
.via("associateTxn"),
validateChargedUsdWithin(
"associateTxn",
EXPECTED_ASSOCIATE_TOKEN_PRICE * tokens.size(),
ALLOWED_DIFFERENCE_PERCENTAGE)));
}

private Stream<DynamicTest> updateBulkNftTokensAndValidateFees(
final int mintAmount, final List<Long> updateAmounts) {
final var supplyKey = "supplyKey";
return hapiTest(
newKeyNamed(supplyKey),
cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).key(supplyKey),
tokenCreate(NFT_TOKEN)
.treasury(OWNER)
.tokenType(NON_FUNGIBLE_UNIQUE)
.supplyKey(supplyKey)
.supplyType(TokenSupplyType.INFINITE)
.initialSupply(0),
mintToken(
NFT_TOKEN,
IntStream.range(0, mintAmount)
.mapToObj(a -> ByteString.copyFromUtf8(String.valueOf(a)))
.toList())
.payingWith(OWNER)
.signedBy(supplyKey)
.blankMemo(),
tokenUpdateNfts(NFT_TOKEN, TOKEN_UPDATE_METADATA, updateAmounts)
.payingWith(OWNER)
.signedBy(supplyKey)
.blankMemo()
.via("updateTxn"),
validateChargedUsdWithin(
"updateTxn",
EXPECTED_NFT_UPDATE_PRICE * updateAmounts.size(),
ALLOWED_DIFFERENCE_PERCENTAGE));
}
}

private String txnFor(String tokenSubType) {
return tokenSubType + "Txn";
}
Expand Down

0 comments on commit 59be979

Please sign in to comment.