From 47a08efcc9fb06bcf1d42fe9ffd36b26a6a78286 Mon Sep 17 00:00:00 2001
From: Simeon Nakov <simeon.nakov@limechain.tech>
Date: Fri, 31 Jan 2025 18:55:58 +0200
Subject: [PATCH] requested changes from PR

Signed-off-by: Simeon Nakov <simeon.nakov@limechain.tech>
---
 test/constants.js                             |   2 +
 .../hrc-904/AirdropContract.js                | 167 +++++++++++-------
 .../hedera-token-service/utils.js             |  58 ++++++
 3 files changed, 161 insertions(+), 66 deletions(-)

diff --git a/test/constants.js b/test/constants.js
index ebfc0b517..dba50a54b 100644
--- a/test/constants.js
+++ b/test/constants.js
@@ -216,6 +216,7 @@ const GAS_LIMIT_5_000_000 = { gasLimit: 5_000_000 };
 const GAS_LIMIT_10_000_000 = { gasLimit: 10_000_000 };
 const GAS_LIMIT_800000 = { gasLimit: 800000 };
 const GAS_LIMIT_8000000 = { gasLimit: 8000000 };
+const ONE_HBAR = ethers.parseEther('1');
 const TOKEN_NAME = 'tokenName';
 const TOKEN_SYMBOL = 'tokenSymbol';
 const TOKEN_URL = 'tokenUrl';
@@ -254,4 +255,5 @@ module.exports = {
   GWEI,
   HTS_SYSTEM_CONTRACT_ID,
   HAS_SYSTEM_CONTRACT_ID,
+  ONE_HBAR,
 };
diff --git a/test/system-contracts/hedera-token-service/hrc-904/AirdropContract.js b/test/system-contracts/hedera-token-service/hrc-904/AirdropContract.js
index b0cad7efa..681f739ef 100644
--- a/test/system-contracts/hedera-token-service/hrc-904/AirdropContract.js
+++ b/test/system-contracts/hedera-token-service/hrc-904/AirdropContract.js
@@ -33,60 +33,7 @@ describe('HIP904 AirdropContract Test Suite', function () {
   let signers;
   let owner;
   let accounts;
-
-  async function setupToken() {
-    const tokenAddress =
-      await utils.createFungibleTokenWithSECP256K1AdminKeyWithoutKYC(
-        tokenCreateContract,
-        owner,
-        utils.getSignerCompressedPublicKey()
-      );
-
-    await utils.updateTokenKeysViaHapi(tokenAddress, [
-      await airdropContract.getAddress(),
-      await tokenCreateContract.getAddress(),
-    ]);
-
-    await utils.associateToken(
-      tokenCreateContract,
-      tokenAddress,
-      Constants.Contract.TokenCreateContract
-    );
-
-    return tokenAddress;
-  }
-
-  async function setupNft() {
-    const nftTokenAddress =
-      await utils.createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYC(
-        tokenCreateContract,
-        owner,
-        utils.getSignerCompressedPublicKey()
-      );
-
-    await utils.updateTokenKeysViaHapi(
-      nftTokenAddress,
-      [
-        await airdropContract.getAddress(),
-        await tokenCreateContract.getAddress(),
-      ],
-      true,
-      true,
-      false,
-      true,
-      true,
-      true,
-      false
-    );
-
-    await utils.associateToken(
-      tokenCreateContract,
-      nftTokenAddress,
-      Constants.Contract.TokenCreateContract
-    );
-
-    return nftTokenAddress;
-  }
+  let contractAddresses;
 
   before(async function () {
     signers = await ethers.getSigners();
@@ -103,19 +50,32 @@ describe('HIP904 AirdropContract Test Suite', function () {
     owner = signers[0].address;
     accounts = signers.slice(1, 3).map((s) => s.address);
 
-    await utils.updateAccountKeysViaHapi([
+    contractAddresses = [
       await airdropContract.getAddress(),
       await tokenCreateContract.getAddress(),
-    ]);
+    ];
+    await utils.updateAccountKeysViaHapi(contractAddresses);
 
-    tokenAddress = await setupToken();
-    nftTokenAddress = await setupNft();
+    tokenAddress = await utils.setupToken(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
+    nftTokenAddress = await utils.setupNft(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
   });
 
   it('should airdrop a fungible token (FT) to a single account', async function () {
     const ftAmount = BigInt(1);
     const receiver = signers[1].address;
-    const tokenAddress = await setupToken();
+    const tokenAddress = await utils.setupToken(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
 
     const initialBalance = await erc20Contract.balanceOf(
       tokenAddress,
@@ -162,7 +122,11 @@ describe('HIP904 AirdropContract Test Suite', function () {
   it('should airdrop fungible token (FT) to a single account using distribute', async function () {
     const ftAmount = BigInt(1);
     const receiver = signers[1].address;
-    const tokenAddress = await setupToken();
+    const tokenAddress = await utils.setupToken(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
 
     const initialBalance = await erc20Contract.balanceOf(
       tokenAddress,
@@ -187,7 +151,11 @@ describe('HIP904 AirdropContract Test Suite', function () {
 
   it('should airdrop fungible tokens (FT) to multiple accounts', async function () {
     const ftAmount = BigInt(1);
-    const tokenAddress = await setupToken();
+    const tokenAddress = await utils.setupToken(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
 
     const getBalances = async () =>
       Promise.all(
@@ -234,8 +202,14 @@ describe('HIP904 AirdropContract Test Suite', function () {
     expect(nftOwner).to.equal(receiver);
   });
 
+  // TODO: Test skipped due to missing error code support in services implementation
+  // See: https://github.com/hashgraph/hedera-services/issues/17409
   it.skip('should airdrop non-fungible tokens (NFT) to multiple accounts', async function () {
-    const nftTokenAddress = await setupNft();
+    const nftTokenAddress = await utils.setupNft(
+      tokenCreateContract,
+      owner,
+      contractAddresses
+    );
     const serials = [];
     serials.push(
       await utils.mintNFTToAddress(tokenCreateContract, nftTokenAddress)
@@ -269,7 +243,9 @@ describe('HIP904 AirdropContract Test Suite', function () {
     const tokens = [];
     // Every accountAmount counts as 1 transfer so 5x2=10
     for (let i = 0; i < 5; i++) {
-      tokens.push(await setupToken());
+      tokens.push(
+        await utils.setupToken(tokenCreateContract, owner, contractAddresses)
+      );
     }
     for (let i = 0; i < accounts.length; i++) {
       const tx = await airdropContract.multipleFtAirdrop(
@@ -292,7 +268,11 @@ describe('HIP904 AirdropContract Test Suite', function () {
       const tokens = [];
       const serials = [];
       for (let i = 0; i < count; i++) {
-        const tokenAddress = await setupNft();
+        const tokenAddress = await utils.setupNft(
+          tokenCreateContract,
+          owner,
+          contractAddresses
+        );
         const serial = await utils.mintNFTToAddress(
           tokenCreateContract,
           tokenAddress
@@ -400,7 +380,11 @@ describe('HIP904 AirdropContract Test Suite', function () {
       const nftTokens = [];
       const nftSerials = [];
       for (let i = 0; i < 11; i++) {
-        const tokenAddress = await setupNft();
+        const tokenAddress = await utils.setupNft(
+          tokenCreateContract,
+          owner,
+          contractAddresses
+        );
         const serial = await utils.mintNFTToAddress(
           tokenCreateContract,
           tokenAddress
@@ -432,7 +416,9 @@ describe('HIP904 AirdropContract Test Suite', function () {
       const ftAmount = BigInt(1);
       const tokens = [];
       for (let i = 0; i < 6; i++) {
-        tokens.push(await setupToken());
+        tokens.push(
+          await utils.setupToken(tokenCreateContract, airdropContract, owner)
+        );
       }
       const tx = await airdropContract.multipleFtAirdrop(
         tokens,
@@ -447,4 +433,53 @@ describe('HIP904 AirdropContract Test Suite', function () {
       expect(error.shortMessage).to.eq('transaction execution reverted');
     }
   });
+
+  it('should handle airdrop to account with no available association slots', async function () {
+    const ftAmount = BigInt(1);
+    const receiver = ethers.Wallet.createRandom().connect(ethers.provider);
+    await signers[0].sendTransaction({
+      to: receiver.address,
+      value: ethers.parseEther('100'),
+    });
+    const IHRC904AccountFacade = new ethers.Interface(
+      (await hre.artifacts.readArtifact('IHRC904AccountFacade')).abi
+    );
+
+    walletIHRC904AccountFacade = new ethers.Contract(
+      receiver.address,
+      IHRC904AccountFacade,
+      receiver
+    );
+
+    const disableAutoAssociations =
+      await walletIHRC904AccountFacade.setUnlimitedAutomaticAssociations(
+        false,
+        {
+          gasLimit: 2_000_000,
+        }
+      );
+    await disableAutoAssociations.wait();
+
+    const tx = await airdropContract.tokenAirdrop(
+      tokenAddress,
+      signers[0].address,
+      receiver.address,
+      ftAmount,
+      {
+        gasLimit: 2_000_000,
+        value: Constants.ONE_HBAR,
+      }
+    );
+    await tx.wait();
+
+    const responseCode = await utils.getHTSResponseCode(tx.hash);
+    expect(responseCode).to.eq('22');
+
+    // The airdrop will be pending, so the balance should still be 0
+    const balance = await erc20Contract.balanceOf(
+      tokenAddress,
+      receiver.address
+    );
+    expect(balance).to.equal(0n);
+  });
 });
diff --git a/test/system-contracts/hedera-token-service/utils.js b/test/system-contracts/hedera-token-service/utils.js
index 5b028849d..77ae009c6 100644
--- a/test/system-contracts/hedera-token-service/utils.js
+++ b/test/system-contracts/hedera-token-service/utils.js
@@ -1000,6 +1000,64 @@ class Utils {
     );
     return BigInt(precompileAction.result_data).toString();
   }
+
+  static async setupNft(tokenCreateContract, owner, contractAddresses) {
+    const nftTokenAddress =
+      await this.createNonFungibleTokenWithSECP256K1AdminKeyWithoutKYC(
+        tokenCreateContract,
+        owner,
+        this.getSignerCompressedPublicKey()
+      );
+
+    await this.updateTokenKeysViaHapi(
+      nftTokenAddress,
+      contractAddresses,
+      true,
+      true,
+      false,
+      true,
+      true,
+      true,
+      false
+    );
+
+    await this.associateToken(
+      tokenCreateContract,
+      nftTokenAddress,
+      Constants.Contract.TokenCreateContract
+    );
+
+    return nftTokenAddress;
+  }
+
+  static async setupToken(tokenCreateContract, owner, contractAddresses) {
+    const tokenAddress =
+      await this.createFungibleTokenWithSECP256K1AdminKeyWithoutKYC(
+        tokenCreateContract,
+        owner,
+        this.getSignerCompressedPublicKey()
+      );
+
+    await this.updateTokenKeysViaHapi(
+      tokenAddress,
+      contractAddresses,
+      true,
+      true,
+      false,
+      true,
+      true,
+      true,
+      false
+    );
+
+    await this.associateToken(
+      tokenCreateContract,
+      tokenAddress,
+      Constants.Contract.TokenCreateContract
+    );
+
+    return tokenAddress;
+  }
 }
 
 module.exports = Utils;