Skip to content

Commit

Permalink
feat: nft - sample usage of hedera hts forking in hardhat and foundry…
Browse files Browse the repository at this point in the history
… when testing nft (#191)

Signed-off-by: Mariusz Jasuwienas <mariusz.jasuwienas@arianelabs.com>
  • Loading branch information
arianejasuwienas committed Jan 21, 2025
1 parent 8533630 commit 5d8b6f7
Show file tree
Hide file tree
Showing 11 changed files with 420 additions and 7 deletions.
28 changes: 28 additions & 0 deletions examples/foundry-hts/NFT.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.4970613
address NFT_mainnet = 0x00000000000000000000000000000000004Bd875;

address private user;

function setUp() external {
htsSetup();

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

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

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.4970613
address NFT_mainnet = 0x00000000000000000000000000000000004Bd875;

string memory name = IERC721(NFT_mainnet).name();
string memory symbol = IERC721(NFT_mainnet).symbol();
string memory tokenURI = IERC721(NFT_mainnet).tokenURI(1);
assertEq(name, "Concierge Collectibles");
assertEq(symbol, "Concierge Collectibles");
assertEq(tokenURI, "ipfs://QmVtsRvgZkqbBr5h5NB17LntAWC9DgXToLTLhNKCzB9RHZ");
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');
});
});
31 changes: 31 additions & 0 deletions examples/hardhat-hts/test/nft-ownerof.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*-
* 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');
expect(await nft['ownerOf'](1)).to.not.be.equal(
'0x0000000000000000000000000000000000000000'
);
});
});
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 5d8b6f7

Please sign in to comment.