Skip to content

Commit

Permalink
feat: example usage in nft testing of our foundry library and hardhat…
Browse files Browse the repository at this point in the history
… plugin (#191) (#209)

Signed-off-by: Mariusz Jasuwienas <[email protected]>
  • Loading branch information
arianejasuwienas authored Feb 4, 2025
1 parent e4a7e92 commit 2e3bd9b
Show file tree
Hide file tree
Showing 12 changed files with 450 additions and 10 deletions.
20 changes: 17 additions & 3 deletions contracts/HtsSystemContractJson.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ contract HtsSystemContractJson is HtsSystemContract {
tokenInfo.token = _getHederaToken(json);
tokenInfo.fixedFees = _getFixedFees(json);
tokenInfo.fractionalFees = _getFractionalFees(json);
tokenInfo.royaltyFees = _getRoyaltyFees(json);
tokenInfo.royaltyFees = _getRoyaltyFees(_sanitizeFeesStructure(json));
tokenInfo.ledgerId = _getLedgerId();
tokenInfo.defaultKycStatus = false; // not available in the fetched JSON from mirror node
tokenInfo.totalSupply = int64(vm.parseInt(vm.parseJsonString(json, ".total_supply")));
Expand All @@ -226,6 +226,16 @@ contract HtsSystemContractJson is HtsSystemContract {
return tokenInfo;
}

// In order to properly decode the bytes returned by the parseJson into the Solidity Structure, the full,
// correct structure has to be provided in the input json, with all of the corresponding fields.
function _sanitizeFeesStructure(string memory json) private pure returns (string memory) {
return vm.replace(
json,
"\"fallback_fee\":null}",
"\"fallback_fee\":{\"amount\":0,\"denominating_token_id\":\"\"}}"
);
}

function _getHederaToken(string memory json) private returns (HederaToken memory token) {
token.tokenKeys = _getTokenKeys(json);
token.name = vm.parseJsonString(json, ".name");
Expand Down Expand Up @@ -394,7 +404,6 @@ contract HtsSystemContractJson is HtsSystemContract {
if (!vm.keyExistsJson(json, ".custom_fees.royalty_fees")) {
return new RoyaltyFee[](0);
}

try vm.parseJson(json, ".custom_fees.royalty_fees") returns (bytes memory royaltyFeesBytes) {
if (royaltyFeesBytes.length == 0) {
return new RoyaltyFee[](0);
Expand All @@ -404,11 +413,16 @@ contract HtsSystemContractJson is HtsSystemContract {
for (uint i = 0; i < fees.length; i++) {
string memory path = vm.replace(".custom_fees.royalty_fees[{i}]", "{i}", vm.toString(i));
address collectorAccount = mirrorNode().getAccountAddress(vm.parseJsonString(json, string.concat(path, ".collector_account_id")));
bytes memory denominatingTokenBytes = vm.parseJson(json, string.concat(path, ".denominating_token_id"));
address denominatingToken;
if (keccak256(denominatingTokenBytes) != keccak256("")) {
denominatingToken = mirrorNode().getAccountAddress(vm.parseJsonString(json, string.concat(path, ".denominating_token_id")));
}
royaltyFees[i] = RoyaltyFee(
int64(vm.parseJsonInt(json, string.concat(path, ".amount.numerator"))),
int64(vm.parseJsonInt(json, string.concat(path, ".amount.denominator"))),
int64(vm.parseJsonInt(json, string.concat(path, ".fallback_fee.amount"))),
mirrorNode().getAccountAddress(vm.parseJsonString(json, string.concat(path, ".denominating_token_id"))),
denominatingToken,
collectorAccount == address(0),
collectorAccount
);
Expand Down
33 changes: 33 additions & 0 deletions examples/foundry-hts/NFT.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {Test} from "forge-std/Test.sol";
import {htsSetup} from "hedera-forking/contracts/htsSetup.sol";
import {IERC721} from "hedera-forking/contracts/IERC721.sol";

contract NFTExampleTest is Test {
// https://hashscan.io/mainnet/token/0.0.5083205
address NFT_mainnet = 0x00000000000000000000000000000000004d9045;

address private user;

function setUp() external {
htsSetup();

user = makeAddr("user");
dealERC721(NFT_mainnet, user, 3);
}

function test_get_owner_of_existing_account() view external {
address owner = IERC721(NFT_mainnet).ownerOf(1);
assertNotEq(IERC721(NFT_mainnet).ownerOf(1), address(0));

// The assertion below cannot be guaranteed, since we can only query the current owner of the NFT,
// Note that the ownership of the NFT may change over time.
assertEq(owner, 0x000000000000000000000000000000000006889a);
}

function test_dealt_nft_assigned_to_local_account() view external {
assertEq(IERC721(NFT_mainnet).ownerOf(3), user);
}
}
25 changes: 25 additions & 0 deletions examples/foundry-hts/NFTConsole.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {Test, console} from "forge-std/Test.sol";
import {htsSetup} from "hedera-forking/contracts/htsSetup.sol";
import {IERC721} from "hedera-forking/contracts/IERC721.sol";

contract NFTConsoleExampleTest is Test {
function setUp() external {
htsSetup();
}

function test_using_console_log() view external {
// https://hashscan.io/mainnet/token/0.0.5083205
address NFT_mainnet = 0x00000000000000000000000000000000004d9045;

string memory name = IERC721(NFT_mainnet).name();
string memory symbol = IERC721(NFT_mainnet).symbol();
string memory tokenURI = IERC721(NFT_mainnet).tokenURI(1);
assertEq(name, "THE BARKANEERS");
assertEq(symbol, "BARKANEERS");
assertEq(tokenURI, "ipfs://bafkreif4hpsgflzzvd7c4abx5u5xwrrjl7wkimbjtndvkxodklxdam5upm");
console.log("name: %s, symbol: %s, tokenURI: %s", name, symbol, tokenURI);
}
}
19 changes: 19 additions & 0 deletions examples/hardhat-hts/contracts/CallNFT.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {IERC721} from "./IERC721.sol";
import {console} from "hardhat/console.sol";

contract CallNFT {
function getTokenName(address tokenAddress) external view returns (string memory) {
return IERC721(tokenAddress).name();
}

function invokeTransferFrom(address tokenAddress, address to, uint256 serialId) external {
// You can use `console.log` as usual
// https://hardhat.org/tutorial/debugging-with-hardhat-network#solidity--console.log
console.log("Transferring from %s to %s %s tokens", msg.sender, to, serialId);
address owner = IERC721(tokenAddress).ownerOf(serialId);
IERC721(tokenAddress).transferFrom(owner, to, serialId);
}
}
103 changes: 103 additions & 0 deletions examples/hardhat-hts/contracts/IERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

/**
* See https://ethereum.org/en/developers/docs/standards/tokens/erc-721/#events for more information.
*/
interface IERC721Events {
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}

/**
* This interface is used to get the selectors and for testing.
*
* https://hips.hedera.com/hip/hip-218
* https://hips.hedera.com/hip/hip-376
*/
interface IERC721 {
/**
* @dev Returns the token collection name.
*/
function name() external view returns (string memory);

/**
* @dev Returns the token collection symbol.
*/
function symbol() external view returns (string memory);

/**
* @dev Returns the Uniform Resource Identifier (URI) for `serialId` token.
*/
function tokenURI(uint256 serialId) external view returns (string memory);

/**
* @dev Returns the total amount of tokens stored by the contract.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the number of tokens in `owner`'s account.
*/
function balanceOf(address owner) external view returns (uint256 balance);

/**
* @dev Returns the owner of the `serialId` token.
*
* Requirements:
* - `serialId` must exist.
*/
function ownerOf(uint256 serialId) external view returns (address);

/**
* @dev Transfers `serialId` token from `sender` to `recipient`.
*
* Requirements:
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `serialId` token must be owned by `sender`.
* - If the caller is not `sender`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/
function transferFrom(address sender, address recipient, uint256 serialId) external payable;

/**
* @dev Gives permission to `spender` to transfer `serialId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
* - The caller must own the token or be an approved operator.
* - `serialId` must exist.
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 serialId) external payable;

/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/
function setApprovalForAll(address operator, bool approved) external;

/**
* @dev Returns the account approved for `serialId` token.
*
* Requirements:
* - `serialId` must exist.
*/
function getApproved(uint256 serialId) external view returns (address operator);

/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
4 changes: 2 additions & 2 deletions examples/hardhat-hts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions examples/hardhat-hts/test/nft-info.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*-
* Hedera Hardhat Forking Plugin
*
* Copyright (C) 2024 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');
// prettier-ignore
const { ethers: { getContractAt } } = require('hardhat');

describe('NFT example -- informational', function () {
it('should get name and symbol', async function () {
// https://hashscan.io/mainnet/token/0.0.4970613
const nft = await getContractAt('IERC721', '0x00000000000000000000000000000000004bd875');
expect(await nft['name']()).to.be.equal('Concierge Collectibles');
expect(await nft['symbol']()).to.be.equal('Concierge Collectibles');
});
});
32 changes: 32 additions & 0 deletions examples/hardhat-hts/test/nft-ownerof.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*-
* Hedera Hardhat Forking Plugin
*
* Copyright (C) 2024 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');
// prettier-ignore
const { ethers: { getContractAt } } = require('hardhat');

describe('NFT example -- ownerOf', function () {
it('should get `ownerOf` account holder', async function () {
// https://hashscan.io/mainnet/token/0.0.4970613
const nft = await getContractAt('IERC721', '0x00000000000000000000000000000000004bd875');

// The assertion below cannot be guaranteed, since we can only query the current owner of the NFT,
// Note that the ownership of the NFT may change over time.
expect(await nft['ownerOf'](1)).to.be.equal('0x0000000000000000000000000000000000161927');
});
});
47 changes: 47 additions & 0 deletions examples/hardhat-hts/test/nft-transfer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*-
* Hedera Hardhat Forking Plugin
*
* Copyright (C) 2024 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');
// prettier-ignore
const { ethers: { getSigner, getSigners, getContractAt }, network: { provider } } = require('hardhat');
const { loadFixture } = require('@nomicfoundation/hardhat-toolbox/network-helpers');

describe('NFT example -- transferFrom', function () {
async function id() {
return [(await getSigners())[0]];
}

it("should `transferFrom` tokens from account holder to one of Hardhat' signers", async function () {
const [receiver] = await loadFixture(id);

// https://hashscan.io/mainnet/token/0.0.4970613
const nft = await getContractAt('IERC721', '0x00000000000000000000000000000000004bd875');

const holderAddress = await nft['ownerOf'](1n);

await provider.request({
method: 'hardhat_impersonateAccount',
params: [holderAddress],
});

const holder = await getSigner(holderAddress);
await nft.connect(holder)['transferFrom'](holder, receiver, 1n);

expect(await nft['ownerOf'](1n)).to.be.equal(receiver.address);
});
});
Loading

0 comments on commit 2e3bd9b

Please sign in to comment.