Skip to content

Commit

Permalink
fix: Fix hollow account completion when completing with nft transfer … (
Browse files Browse the repository at this point in the history
#13837)

Signed-off-by: Kristiyan Selveliev <[email protected]>
  • Loading branch information
kselveliev authored Jun 17, 2024
1 parent 975f9e2 commit 49af179
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -510,9 +510,13 @@ private void checkSender(
// If the sender account is immutable, then we throw an exception.
final var key = senderAccount.key();
if (key == null || !isValid(key)) {
// If the sender account has no key, then fail with INVALID_ACCOUNT_ID.
// NOTE: should change to ACCOUNT_IS_IMMUTABLE
throw new PreCheckException(INVALID_ACCOUNT_ID);
if (isHollow(senderAccount)) {
meta.requireSignatureForHollowAccount(senderAccount);
} else {
// If the sender account has no key, then fail with INVALID_ACCOUNT_ID.
// NOTE: should change to ACCOUNT_IS_IMMUTABLE
throw new PreCheckException(INVALID_ACCOUNT_ID);
}
} else if (!nftTransfer.isApproval()) {
meta.requireKey(key);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.hedera.services.bdd.suites.crypto;

import static com.google.protobuf.ByteString.copyFromUtf8;
import static com.hedera.node.app.service.evm.utils.EthSigsUtils.recoverAddressFromPubKey;
import static com.hedera.services.bdd.junit.TestTags.CRYPTO;
import static com.hedera.services.bdd.spec.HapiPropertySource.accountIdFromHexedMirrorAddress;
import static com.hedera.services.bdd.spec.HapiPropertySource.asAccountString;
Expand All @@ -35,9 +36,11 @@
import static com.hedera.services.bdd.spec.keys.KeyShape.threshOf;
import static com.hedera.services.bdd.spec.keys.SigControl.OFF;
import static com.hedera.services.bdd.spec.keys.SigControl.ON;
import static com.hedera.services.bdd.spec.keys.TrieSigMapGenerator.uniqueWithFullPrefixesFor;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAliasedAccountInfo;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getContractInfo;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt;
import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord;
Expand Down Expand Up @@ -105,13 +108,17 @@
import static com.hedera.services.bdd.suites.HapiSuite.NODE_REWARD;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS;
import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS;
import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SHAPE;
import static com.hedera.services.bdd.suites.HapiSuite.STAKING_REWARD;
import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY;
import static com.hedera.services.bdd.suites.contract.Utils.aaWith;
import static com.hedera.services.bdd.suites.contract.Utils.accountId;
import static com.hedera.services.bdd.suites.contract.Utils.captureOneChildCreate2MetaFor;
import static com.hedera.services.bdd.suites.contract.Utils.mirrorAddrWith;
import static com.hedera.services.bdd.suites.contract.Utils.ocWith;
import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.A_TOKEN;
import static com.hedera.services.bdd.suites.crypto.AutoAccountCreationSuite.PARTY;
import static com.hedera.services.bdd.suites.file.FileUpdateSuite.CIVILIAN;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN;
import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN;
Expand Down Expand Up @@ -2297,4 +2304,67 @@ final Stream<DynamicTest> customFeesCannotCauseOverflow() {
.then(cryptoTransfer(moving(1L, "ft").between(PARTY, COUNTERPARTY))
.hasKnownStatus(INSUFFICIENT_SENDER_ACCOUNT_BALANCE_FOR_CUSTOM_FEE));
}

@HapiTest
final Stream<DynamicTest> createHollowAccountWithNftTransferAndCompleteIt() {
final var tokenA = "tokenA";
final var hollowAccountKey = "hollowAccountKey";
final var transferTokenAAndBToHollowAccountTxn = "transferTokenAToHollowAccountTxn";
final AtomicReference<TokenID> tokenIdA = new AtomicReference<>();
final AtomicReference<ByteString> treasuryAlias = new AtomicReference<>();
final AtomicReference<ByteString> hollowAccountAlias = new AtomicReference<>();

return defaultHapiSpec("createHollowAccountWithNftTransferAndCompleteIt")
.given(
newKeyNamed(hollowAccountKey).shape(SECP_256K1_SHAPE),
newKeyNamed(MULTI_KEY),
cryptoCreate(TREASURY).balance(10_000 * ONE_MILLION_HBARS),
tokenCreate(tokenA)
.tokenType(NON_FUNGIBLE_UNIQUE)
.initialSupply(0L)
.supplyKey(MULTI_KEY)
.treasury(TREASURY),
// Mint NFTs
mintToken(tokenA, List.of(ByteString.copyFromUtf8("metadata1"))),
mintToken(tokenA, List.of(ByteString.copyFromUtf8("metadata2"))),
withOpContext((spec, opLog) -> {
final var registry = spec.registry();
final var treasuryAccountId = registry.getAccountID(TREASURY);
treasuryAlias.set(ByteString.copyFrom(asSolidityAddress(treasuryAccountId)));

// Save the alias for the hollow account
final var ecdsaKey = spec.registry()
.getKey(hollowAccountKey)
.getECDSASecp256K1()
.toByteArray();
final var evmAddressBytes = ByteString.copyFrom(recoverAddressFromPubKey(ecdsaKey));
hollowAccountAlias.set(evmAddressBytes);
tokenIdA.set(registry.getTokenID(A_TOKEN));
}))
.when(withOpContext((spec, opLog) -> allRunFor(
spec,
// Create hollow account
cryptoTransfer((s, b) -> b.addTokenTransfers(TokenTransferList.newBuilder()
.setToken(tokenIdA.get())
.addNftTransfers(ocWith(
accountId(treasuryAlias.get()),
accountId(hollowAccountAlias.get()),
1L))))
.payingWith(TREASURY)
.signedBy(TREASURY)
.via(transferTokenAAndBToHollowAccountTxn),
// Verify hollow account creation
getAliasedAccountInfo(hollowAccountKey)
.hasToken(relationshipWith(tokenA))
.has(accountWith().hasEmptyKey())
.exposingIdTo(id -> spec.registry().saveAccountId(hollowAccountKey, id)),
// Transfer some hbars to the hollow account so that it could pay the next transaction
cryptoTransfer(movingHbar(ONE_MILLION_HBARS).between(TREASURY, hollowAccountKey)),
// Send transfer to complete the hollow account
cryptoTransfer(movingUnique(tokenA, 1L).between(hollowAccountKey, TREASURY))
.payingWith(hollowAccountKey)
.signedBy(hollowAccountKey, TREASURY)
.sigMapPrefixes(uniqueWithFullPrefixesFor(hollowAccountKey)))))
.then(getAliasedAccountInfo(hollowAccountKey).has(accountWith().key(hollowAccountKey)));
}
}

0 comments on commit 49af179

Please sign in to comment.