Skip to content

Commit

Permalink
feat: adds tests that verify IHRC904 rejectTokens targeted to an EOA …
Browse files Browse the repository at this point in the history
…are functional when called by a contract (#1198)

* feat: added test coverage for TokenReject.sol

Signed-off-by: Simeon Nakov <[email protected]>

* exported magic BigInt as constant

Signed-off-by: Simeon Nakov <[email protected]>

* requested changes from review

Signed-off-by: Simeon Nakov <[email protected]>

* changed ONE_HBAR const to use ethers.parseEther

Signed-off-by: Simeon Nakov <[email protected]>

---------

Signed-off-by: Simeon Nakov <[email protected]>
  • Loading branch information
simzzz authored Feb 4, 2025
1 parent a307013 commit 96941f6
Show file tree
Hide file tree
Showing 3 changed files with 410 additions and 0 deletions.
8 changes: 8 additions & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,19 @@ const Contract = {
CancunOpcodes: 'CancunOpcodes',
KZGPointEvaluation: 'KZGPointEvaluation',
StateRegistry: 'StateRegistry',
Airdrop: 'Airdrop',
TokenReject: 'TokenReject',
AliasAccountUtility: 'AliasAccountUtility',
};

const CALL_EXCEPTION = 'CALL_EXCEPTION';
const CONTRACT_REVERT_EXECUTED_CODE = 3;
const GAS_LIMIT_1_000_000 = { gasLimit: 1_000_000 };
const GAS_LIMIT_2_000_000 = { gasLimit: 2_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';
Expand All @@ -224,6 +228,7 @@ const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
const WEEK = 7 * DAY;
const GWEI = 1e9;
const HTS_SYSTEM_CONTRACT_ID = '0.0.359';

module.exports = {
Events,
Expand All @@ -232,6 +237,7 @@ module.exports = {
CALL_EXCEPTION,
CONTRACT_REVERT_EXECUTED_CODE,
GAS_LIMIT_1_000_000,
GAS_LIMIT_2_000_000,
GAS_LIMIT_10_000_000,
GAS_LIMIT_800000,
GAS_LIMIT_8000000,
Expand All @@ -246,4 +252,6 @@ module.exports = {
WEEK,
WEI,
GWEI,
HTS_SYSTEM_CONTRACT_ID,
ONE_HBAR,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
/*-
*
* Hedera Smart Contracts
*
* Copyright (C) 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.
*
*/

const { expect } = require('chai');
const { ethers } = require('hardhat');
const utils = require('../utils');
const Constants = require('../../../constants');

describe('HIP904 TokenRejectContract Test Suite', function () {
let tokenRejectContract;
let tokenCreateContract;
let airdropContract;
let signers;
let owner;
let receiver;
let walletIHRC904AccountFacade;

before(async function () {
signers = await ethers.getSigners();
tokenRejectContract = await utils.deployContract(
Constants.Contract.TokenReject
);
tokenCreateContract = await utils.deployContract(
Constants.Contract.TokenCreateContract
);
airdropContract = await utils.deployContract(Constants.Contract.Airdrop);
owner = signers[0].address;

const randomWallet = ethers.Wallet.createRandom();
const receiverPrivateKey = randomWallet.privateKey;
receiver = randomWallet.connect(ethers.provider);

await signers[0].sendTransaction({
to: receiver.address,
value: ethers.parseEther('100'),
});

await utils.updateAccountKeysViaHapi([
await tokenRejectContract.getAddress(),
await tokenCreateContract.getAddress(),
await airdropContract.getAddress(),
]);

await utils.updateAccountKeysViaHapi(
[
await tokenRejectContract.getAddress(),
await tokenCreateContract.getAddress(),
await airdropContract.getAddress(),
],
[receiverPrivateKey]
);

const IHRC904AccountFacade = new ethers.Interface(
(await hre.artifacts.readArtifact('IHRC904AccountFacade')).abi
);

walletIHRC904AccountFacade = new ethers.Contract(
receiver.address,
IHRC904AccountFacade,
receiver
);
});

it('should reject tokens for a single account', async function () {
const tokenAddress = await utils.setupToken(
tokenCreateContract,
owner,
airdropContract
);
const receiver = signers[1];

const ftAmount = BigInt(1);
const airdropTx = await airdropContract.tokenAirdrop(
tokenAddress,
owner,
receiver.address,
ftAmount,
{
value: Constants.ONE_HBAR,
gasLimit: 2_000_000,
}
);
await airdropTx.wait();

await walletIHRC904AccountFacade.setUnlimitedAutomaticAssociations(true, {
gasLimit: 2_000_000,
});

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[tokenAddress],
[],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('22'); // SUCCESS code
});

it('should reject NFTs for a single account', async function () {
const nftTokenAddress = await utils.setupNft(
tokenCreateContract,
owner,
airdropContract
);
const receiver = signers[1];

const serial = utils.mintNFTToAddress(tokenCreateContract, nftTokenAddress);

const airdropTx = await airdropContract.nftAirdrop(
nftTokenAddress,
owner,
receiver.address,
serial,
{
value: Constants.ONE_HBAR,
gasLimit: 2_000_000,
}
);
await airdropTx.wait();

await walletIHRC904AccountFacade.setUnlimitedAutomaticAssociations(true, {
gasLimit: 2_000_000,
});

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[],
[nftTokenAddress],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('22'); // SUCCESS code
});

it('should reject tokens for multiple accounts', async function () {
const tokenAddress = await utils.setupToken(
tokenCreateContract,
owner,
airdropContract
);
const receivers = signers.slice(1, 3);

for (const receiver of receivers) {
const airdropTx = await airdropContract.tokenAirdrop(
tokenAddress,
owner,
receiver.address,
BigInt(1),
{
value: Constants.ONE_HBAR,
gasLimit: 2_000_000,
}
);
await airdropTx.wait();

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[tokenAddress],
[],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('22'); // SUCCESS code
}
});

it('should fail when sender does not have any associated tokens', async function () {
const tokenAddress = await utils.setupToken(
tokenCreateContract,
owner,
airdropContract
);

await walletIHRC904AccountFacade.setUnlimitedAutomaticAssociations(false, {
gasLimit: 2_000_000,
});

const airdropTx = await airdropContract.tokenAirdrop(
tokenAddress,
owner,
receiver.address,
BigInt(1),
{
value: Constants.ONE_HBAR,
gasLimit: 2_000_000,
}
);
await airdropTx.wait();

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[tokenAddress],
[],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('184'); // TOKEN_NOT_ASSOCIATED_TO_ACCOUNT code
});

it('should fail when sender does not have a pending airdrop', async function () {
const tokenAddress = await utils.setupToken(
tokenCreateContract,
owner,
airdropContract
);
const receiver = signers[1];

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[tokenAddress],
[],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('178'); // INSUFFICIENT_TOKEN_BALANCE code
});

it('should fail when provided fungible token is invalid', async function () {
const invalidToken = ethers.Wallet.createRandom().address;
const nftTokenAddress = await utils.setupNft(
tokenCreateContract,
owner,
airdropContract
);

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[invalidToken],
[nftTokenAddress],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('167'); // INVALID_TOKEN_ID code
});

it('should fail when provided NFT is invalid', async function () {
const invalidNft = ethers.Wallet.createRandom().address;

const nftTokenAddress = await utils.setupNft(
tokenCreateContract,
owner,
airdropContract
);
const receiver = signers[1];

const serial = utils.mintNFTToAddress(tokenCreateContract, nftTokenAddress);

const airdropTx = await airdropContract.nftAirdrop(
nftTokenAddress,
owner,
receiver.address,
serial,
{
value: Constants.ONE_HBAR,
gasLimit: 2_000_000,
}
);
await airdropTx.wait();

await walletIHRC904AccountFacade.setUnlimitedAutomaticAssociations(true, {
gasLimit: 2_000_000,
});

const tx = await tokenRejectContract.rejectTokens(
receiver.address,
[],
[invalidNft],
Constants.GAS_LIMIT_2_000_000
);
const responseCode = await utils.getHTSResponseCode(tx.hash);
expect(responseCode).to.eq('226'); // INVALID_NFT_ID code
});
});
Loading

0 comments on commit 96941f6

Please sign in to comment.