-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: add additional tests for HIP-904 #17773
base: main
Are you sure you want to change the base?
Changes from 4 commits
425403c
136ee65
274b567
fb5fc76
d5e01a3
bd882be
7965b3e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,23 +16,33 @@ | |
|
||
package com.hedera.services.bdd.suites.contract.precompile.airdrops; | ||
|
||
import static com.hedera.node.app.hapi.utils.EthSigsUtils.recoverAddressFromPubKey; | ||
import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; | ||
import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; | ||
import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingFungiblePendingAirdrop; | ||
import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; | ||
import static com.hedera.services.bdd.spec.dsl.entities.SpecTokenKey.FEE_SCHEDULE_KEY; | ||
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAutoCreatedAccountBalance; | ||
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; | ||
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; | ||
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; | ||
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenClaimAirdrop; | ||
import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFeeScheduleUpdate; | ||
import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fractionalFeeNetOfTransfers; | ||
import static com.hedera.services.bdd.spec.transactions.token.HapiTokenClaimAirdrop.pendingAirdrop; | ||
import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; | ||
import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; | ||
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; | ||
import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; | ||
import static com.hedera.services.bdd.suites.contract.Utils.accountId; | ||
import static com.hedera.services.bdd.suites.contract.Utils.asAddress; | ||
import static com.hedera.services.bdd.suites.contract.precompile.airdrops.SystemContractAirdropHelper.checkForBalances; | ||
import static com.hedera.services.bdd.suites.contract.precompile.airdrops.SystemContractAirdropHelper.checkForEmptyBalance; | ||
import static com.hedera.services.bdd.suites.contract.precompile.airdrops.SystemContractAirdropHelper.prepareAccountAddresses; | ||
import static com.hedera.services.bdd.suites.contract.precompile.airdrops.SystemContractAirdropHelper.prepareContractAddresses; | ||
import static com.hedera.services.bdd.suites.contract.precompile.airdrops.SystemContractAirdropHelper.prepareTokenAddresses; | ||
|
||
import com.google.protobuf.ByteString; | ||
import com.hedera.services.bdd.junit.HapiTest; | ||
import com.hedera.services.bdd.junit.HapiTestLifecycle; | ||
import com.hedera.services.bdd.junit.OrderedInIsolation; | ||
|
@@ -48,6 +58,7 @@ | |
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import java.util.List; | ||
import java.util.OptionalLong; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.DynamicTest; | ||
|
@@ -292,4 +303,264 @@ public Stream<DynamicTest> airdropToAccountWithFreeAutoAssocSlots( | |
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(nft.name(), 1L))); | ||
})); | ||
} | ||
|
||
@Order(7) | ||
@HapiTest | ||
@DisplayName("Contract account airdrops multiple tokens to contract alias with unlimited association slots") | ||
public Stream<DynamicTest> airdropToAccountWithUnlimitedAutoAssocSlots( | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L, maxAutoAssociations = -1) | ||
final SpecContract receiver, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000_000L) final SpecFungibleToken token1, | ||
@NonNull @FungibleToken(initialSupply = 1_000_000L) final SpecFungibleToken token2) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender.authorizeContract(airdropContract), | ||
sender.associateTokens(token1, token2), | ||
token1.treasury().transferUnitsTo(sender, 1_000L, token1), | ||
token2.treasury().transferUnitsTo(sender, 1_000L, token2)); | ||
allRunFor(spec, checkForEmptyBalance(receiver, List.of(token1, token2), List.of())); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call( | ||
"tokenNAmountAirdrops", | ||
prepareTokenAddresses(spec, List.of(token1, token2)), | ||
prepareContractAddresses(spec, List.of(sender, sender)), | ||
prepareContractAddresses(spec, List.of(receiver, receiver)), | ||
10L) | ||
.sending(450_000_000L) | ||
.gas(1_750_000L) | ||
.via("AirdropTxn"), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token1.name(), 10L)), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token2.name(), 10L)), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(0))); | ||
})); | ||
} | ||
|
||
@Order(8) | ||
@HapiTest | ||
@DisplayName("Contract account airdrops a multiple tokens to an account alias without free association slots") | ||
public Stream<DynamicTest> airdropToAccountWithNoFreeAutoAssocSlots( | ||
@NonNull @Account(maxAutoAssociations = 0, tinybarBalance = 100_000_000L) final SpecAccount receiver, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token1, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token2) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender.authorizeContract(airdropContract), | ||
sender.associateTokens(token1, token2), | ||
token1.treasury().transferUnitsTo(sender, 1_000L, token1), | ||
token2.treasury().transferUnitsTo(sender, 1_000L, token2)); | ||
allRunFor(spec, checkForEmptyBalance(receiver, List.of(token1, token2), List.of())); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call( | ||
"tokenNAmountAirdrops", | ||
prepareTokenAddresses(spec, List.of(token1, token2)), | ||
prepareContractAddresses(spec, List.of(sender, sender)), | ||
prepareAccountAddresses(spec, List.of(receiver, receiver)), | ||
10L) | ||
.sending(450_000_000L) | ||
.gas(1_750_000L) | ||
.via("AirdropTxn"), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token1.name(), 0L)), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token2.name(), 0L)), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(2)), | ||
getTxnRecord("AirdropTxn") | ||
.hasChildRecords(recordWith() | ||
.pendingAirdrops(includingFungiblePendingAirdrop( | ||
moving(10L, token1.name()).between(sender.name(), receiver.name()), | ||
moving(10L, token2.name()).between(sender.name(), receiver.name()))))); | ||
})); | ||
} | ||
|
||
@Order(9) | ||
@HapiTest | ||
@DisplayName("Contract account airdrops a multiple tokens to an address with no account on it.") | ||
public Stream<DynamicTest> airdropToAddressWithNoAccount( | ||
@NonNull @Account(maxAutoAssociations = 10, tinybarBalance = 100L) final SpecAccount receiver, | ||
@Contract(contract = "EmptyOne", creationGas = 10_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000_000L) final SpecFungibleToken token) { | ||
|
||
final var hollowAccountKey = "hollowAccountKey"; | ||
final AtomicReference<ByteString> hollowAccountAlias = new AtomicReference<>(); | ||
|
||
return hapiTest( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test only airdrops to a hollow account. You can check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually the description and the method name here were wrong, this test does not need create2 contract. |
||
newKeyNamed(hollowAccountKey).shape(KeyShape.SECP256K1), | ||
sender.getBalance(), | ||
withOpContext((spec, opLog) -> { | ||
final var ecdsaKey = spec.registry() | ||
.getKey(hollowAccountKey) | ||
.getECDSASecp256K1() | ||
.toByteArray(); | ||
final var evmAddressBytes = ByteString.copyFrom(recoverAddressFromPubKey(ecdsaKey)); | ||
hollowAccountAlias.set(evmAddressBytes); | ||
contractCall( | ||
"Airdrop", | ||
"tokenAirdrop", | ||
token, | ||
sender.addressOn(spec.targetNetworkOrThrow()), | ||
asAddress(accountId(hollowAccountAlias.get())), | ||
10L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"); | ||
getAutoCreatedAccountBalance(hollowAccountKey).hasTokenBalance(token.name(), 10L); | ||
})); | ||
} | ||
|
||
@Order(10) | ||
@HapiTest | ||
@DisplayName( | ||
"Contract airdrops a token to an account, then the receiver claims the airdrop, then the sender airdrops the same token again ") | ||
public Stream<DynamicTest> airdropToAccountAgainAfterReceiverClaims( | ||
@NonNull @Account(maxAutoAssociations = 0, tinybarBalance = 100_000_000L) final SpecAccount receiver, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender.authorizeContract(airdropContract), | ||
sender.associateTokens(token), | ||
token.treasury().transferUnitsTo(sender, 10L, token)); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 5L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 0L)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can also verify the PendingAirdrop from the record There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The check is added. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also add the |
||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(1)), | ||
sender.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 10L))); | ||
allRunFor( | ||
spec, | ||
tokenClaimAirdrop(pendingAirdrop(sender.name(), receiver.name(), token.name())) | ||
.payingWith(receiver.name()), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 5L)), | ||
sender.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 5L))); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 3L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 8L)), | ||
sender.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 2L))); | ||
})); | ||
} | ||
|
||
@Order(11) | ||
@HapiTest | ||
@DisplayName("Contract airdrops multiple times that FT to an account, then only one pending transaction is created") | ||
public Stream<DynamicTest> airdropFTToAccountMultipleTimes( | ||
@NonNull @Account(maxAutoAssociations = 0, tinybarBalance = 100_000_000L) final SpecAccount receiver, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender.authorizeContract(airdropContract), | ||
sender.associateTokens(token), | ||
token.treasury().transferUnitsTo(sender, 10L, token)); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also the transaction ids should be different |
||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(1)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 0L))); | ||
allRunFor( | ||
spec, | ||
tokenClaimAirdrop(pendingAirdrop(sender.name(), receiver.name(), token.name())) | ||
.payingWith(receiver.name()), | ||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 3L))); | ||
})); | ||
} | ||
|
||
@Order(12) | ||
@HapiTest | ||
@DisplayName( | ||
"Contract airdrops a FT to an account, then associate the receiver, then airdrops the same token again") | ||
public Stream<DynamicTest> airdropFTToAccountThenAssociateTheReceiverAndAirdropAgain( | ||
@NonNull @Account(maxAutoAssociations = 0, tinybarBalance = 100_000_000L) final SpecAccount receiver, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender.authorizeContract(airdropContract), | ||
sender.associateTokens(token), | ||
token.treasury().transferUnitsTo(sender, 10L, token)); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(1)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here |
||
receiver.associateTokens(token), | ||
airdropContract | ||
.call("tokenAirdrop", token, sender, receiver, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(0)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use different transaction ids as this might result in race conditions and leaky tests |
||
receiver.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 1L))); | ||
})); | ||
} | ||
|
||
@Order(13) | ||
@HapiTest | ||
@DisplayName("Multiple contracts airdrop tokens to multiple accounts.") | ||
public Stream<DynamicTest> multipleContractsAirdropTokensToMultipleAccounts( | ||
@NonNull @Account(maxAutoAssociations = 1, tinybarBalance = 100_000_000L) final SpecAccount receiver1, | ||
@NonNull @Account(maxAutoAssociations = 1, tinybarBalance = 100_000_000L) final SpecAccount receiver2, | ||
@NonNull @Contract(contract = "EmptyOne", creationGas = 100_000_000L) final SpecContract sender1, | ||
@NonNull @Contract(contract = "EmptyConstructor", creationGas = 100_000_000L) final SpecContract sender2, | ||
@NonNull @FungibleToken(initialSupply = 1_000L) final SpecFungibleToken token) { | ||
return hapiTest(withOpContext((spec, opLog) -> { | ||
allRunFor( | ||
spec, | ||
sender1.authorizeContract(airdropContract), | ||
sender2.authorizeContract(airdropContract), | ||
sender1.associateTokens(token), | ||
sender2.associateTokens(token), | ||
token.treasury().transferUnitsTo(sender1, 10L, token), | ||
token.treasury().transferUnitsTo(sender2, 10L, token)); | ||
allRunFor( | ||
spec, | ||
airdropContract | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here it should be only one airdrop with multiple senders and receivers. |
||
.call("tokenAirdrop", token, sender1, receiver1, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L) | ||
.via("AirdropTxn"), | ||
airdropContract | ||
.call("tokenAirdrop", token, sender2, receiver2, 1L) | ||
.sending(85_000_000L) | ||
.gas(1_500_000L), | ||
getTxnRecord("AirdropTxn").hasChildRecords(recordWith().pendingAirdropsCount(0)), | ||
receiver1.getBalance().andAssert(balance -> balance.hasTinyBars(100_000_000L)), | ||
receiver2.getBalance().andAssert(balance -> balance.hasTinyBars(100_000_000L)), | ||
receiver1.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 1L)), | ||
receiver2.getBalance().andAssert(balance -> balance.hasTokenBalance(token.name(), 1L))); | ||
})); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to get the record again, you can just chain the pending checks