From 054a0400c3a785ef87b0e1b8ebcccf1cf3c88305 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Wed, 22 Jan 2025 16:06:40 +0100 Subject: [PATCH 1/6] fix: emit correct transfer and approval events (#194) Signed-off-by: Mariusz Jasuwienas --- contracts/HtsSystemContract.sol | 14 +++++++------- contracts/IERC20.sol | 6 +++--- contracts/IERC721.sol | 4 +++- test/ERC721.t.sol | 6 +++--- test/HTS.t.sol | 18 +++++++++--------- test/Token.t.sol | 6 +++--- 6 files changed, 28 insertions(+), 26 deletions(-) diff --git a/contracts/HtsSystemContract.sol b/contracts/HtsSystemContract.sol index 4d03956b..8c4f5b0b 100644 --- a/contracts/HtsSystemContract.sol +++ b/contracts/HtsSystemContract.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {IERC20Events, IERC20} from "./IERC20.sol"; -import {IERC721, IERC721Events} from "./IERC721.sol"; +import {IERC721Events, IERC721} from "./IERC721.sol"; import {IHRC719} from "./IHRC719.sol"; import {IHederaTokenService} from "./IHederaTokenService.sol"; import {HederaResponseCodes} from "./HederaResponseCodes.sol"; @@ -465,7 +465,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { address to = address(bytes20(msg.data[72:92])); uint256 amount = uint256(bytes32(msg.data[92:124])); _approve(from, to, amount); - emit Approval(from, to, amount); + emit ERC20Approval(from, to, amount); return abi.encode(true); } if (selector == this.approveNFT.selector) { @@ -561,7 +561,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { uint256 amount = uint256(bytes32(msg.data[60:92])); address owner = msg.sender; _approve(owner, spender, amount); - emit Approval(owner, spender, amount); + emit ERC20Approval(owner, spender, amount); return abi.encode(true); } return _redirectForHRC719(selector); @@ -735,7 +735,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { require(from != address(0), "hts: invalid sender"); require(to != address(0), "hts: invalid receiver"); _update(from, to, amount); - emit Transfer(from, to, amount); + emit ERC20Transfer(from, to, amount); } function _transferNFT(address sender, address from, address to, uint256 serialId) private { @@ -763,7 +763,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { // Set the new owner assembly { sstore(slot, to) } - emit Transfer(from, to, serialId); + emit ERC721Transfer(from, to, serialId); } function _update(address from, address to, uint256 amount) public { @@ -809,7 +809,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { address newApproved = isApproved ? spender : address(0); assembly { sstore(slot, newApproved) } - emit Approval(owner, spender, serialId); + emit ERC721Approval(owner, spender, serialId); } /** @@ -830,6 +830,6 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { require(operator != address(0) && operator != sender, "setApprovalForAll: invalid operator"); bytes32 slot = _isApprovedForAllSlot(sender, operator); assembly { sstore(slot, approved) } - emit ApprovalForAll(sender, operator, approved); + emit ERC721ApprovalForAll(sender, operator, approved); } } diff --git a/contracts/IERC20.sol b/contracts/IERC20.sol index 24baa9f3..ad36c972 100644 --- a/contracts/IERC20.sol +++ b/contracts/IERC20.sol @@ -13,13 +13,13 @@ interface IERC20Events { * * Note that `value` may be zero. */ - event Transfer(address indexed from, address indexed to, uint256 amount); + event ERC20Transfer(address indexed from, address indexed to, uint256 amount); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ - event Approval(address indexed owner, address indexed spender, uint256 amount); + event ERC20Approval(address indexed owner, address indexed spender, uint256 amount); } /** @@ -52,7 +52,7 @@ interface IERC20 { /** * @dev Returns the value of tokens owned by `account`. - */ + */ function balanceOf(address account) external view returns (uint256); /** diff --git a/contracts/IERC721.sol b/contracts/IERC721.sol index a7189e07..3f2f9069 100644 --- a/contracts/IERC721.sol +++ b/contracts/IERC721.sol @@ -5,7 +5,9 @@ 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); + event ERC721Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event ERC721Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ERC721ApprovalForAll(address indexed owner, address indexed operator, bool approved); } /** diff --git a/test/ERC721.t.sol b/test/ERC721.t.sol index b5cac6b9..e6272286 100644 --- a/test/ERC721.t.sol +++ b/test/ERC721.t.sol @@ -39,7 +39,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit Transfer(CFNFTFF_TREASURY, to, tokenId); + emit ERC721Transfer(CFNFTFF_TREASURY, to, tokenId); IERC721(CFNFTFF).transferFrom(CFNFTFF_TREASURY, to, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(tokenId), to); @@ -50,7 +50,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit Approval(CFNFTFF_TREASURY, spender, tokenId); + emit ERC721Approval(CFNFTFF_TREASURY, spender, tokenId); IERC721(CFNFTFF).approve(spender, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).getApproved(tokenId), spender); @@ -60,7 +60,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { address operator = makeAddr("operator"); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit ApprovalForAll(CFNFTFF_TREASURY, operator, true); + emit ERC721ApprovalForAll(CFNFTFF_TREASURY, operator, true); IERC721(CFNFTFF).setApprovalForAll(operator, true); assertTrue(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); } diff --git a/test/HTS.t.sol b/test/HTS.t.sol index 5b54e04d..36c5dfc2 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -631,7 +631,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.Transfer(owner, to, amount); + emit IERC20Events.ERC20Transfer(owner, to, amount); IHederaTokenService(HTS_ADDRESS).transferToken(USDC, owner, to, int64(int256(amount))); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -650,7 +650,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.Transfer(owner, to, amount); + emit IERC20Events.ERC20Transfer(owner, to, amount); IHederaTokenService(HTS_ADDRESS).transferFrom(USDC, owner, to, amount); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -675,7 +675,7 @@ contract HTSTest is Test, TestSetup { uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); + emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, CFNFTFF_TREASURY, to, int64(int256(serialId))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -686,7 +686,7 @@ contract HTSTest is Test, TestSetup { uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); + emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferFromNFT(CFNFTFF, CFNFTFF_TREASURY, to, serialId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -707,7 +707,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.Transfer(owner, to[0], amount); + emit IERC20Events.ERC20Transfer(owner, to[0], amount); IHederaTokenService(HTS_ADDRESS).transferTokens(USDC, to, amounts); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -759,7 +759,7 @@ contract HTSTest is Test, TestSetup { to[0] = makeAddr("recipient"); vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); + emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, from[0], to[0], int64(int256(serialId[0]))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId[0]), to[0]); @@ -800,7 +800,7 @@ contract HTSTest is Test, TestSetup { assertNotEq(IERC721(token).getApproved(1), newSpender); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(token); - emit IERC20Events.Approval(CFNFTFF_TREASURY, newSpender, 1); + emit IERC721Events.ERC721Approval(CFNFTFF_TREASURY, newSpender, 1); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approveNFT(token, newSpender, 1); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC721(token).getApproved(1), newSpender); @@ -813,7 +813,7 @@ contract HTSTest is Test, TestSetup { assertEq(IERC20(USDC).allowance(owner, spender), 0); vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.Approval(owner, spender, amount); + emit IERC20Events.ERC20Approval(owner, spender, amount); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approve(USDC, spender, amount); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC20(USDC).allowance(owner, spender), amount); @@ -824,7 +824,7 @@ contract HTSTest is Test, TestSetup { assertFalse(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC721Events.ApprovalForAll(CFNFTFF_TREASURY, operator, true); + emit IERC721Events.ERC721ApprovalForAll(CFNFTFF_TREASURY, operator, true); int64 setApprovalForAllResponseCode = HtsSystemContract(HTS_ADDRESS) .setApprovalForAll(CFNFTFF, operator, true); assertEq(setApprovalForAllResponseCode, HederaResponseCodes.SUCCESS); diff --git a/test/Token.t.sol b/test/Token.t.sol index 93ddbceb..bf50d996 100644 --- a/test/Token.t.sol +++ b/test/Token.t.sol @@ -114,7 +114,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(owner); vm.expectEmit(USDC); - emit Approval(owner, spender, amount); + emit ERC20Approval(owner, spender, amount); IERC20(USDC).approve(spender, amount); assertEq(IERC20(USDC).allowance(owner, spender), amount); @@ -142,7 +142,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(owner); // https://book.getfoundry.sh/cheatcodes/prank vm.expectEmit(USDC); - emit Transfer(owner, to, amount); + emit ERC20Transfer(owner, to, amount); IERC20(USDC).transfer(to, amount); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -186,7 +186,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(bob); vm.expectEmit(USDC); - emit Transfer(alice, charlie, transferAmount); + emit ERC20Transfer(alice, charlie, transferAmount); IERC20(USDC).transferFrom(alice, charlie, transferAmount); assertEq(IERC20(USDC).balanceOf(alice), 54_300000 - 4_000000); From 7798a0a2a0ea3c8ee491274a05604802a824a9a0 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 23 Jan 2025 10:26:54 +0100 Subject: [PATCH 2/6] feat: keeping the initial selectors of the events (#194) Signed-off-by: Mariusz Jasuwienas --- contracts/HtsSystemContract.sol | 15 +++++++-------- contracts/IERC20.sol | 4 ++-- contracts/IERC721.sol | 6 +++--- test/ERC721.t.sol | 12 ++++++------ test/HTS.t.sol | 18 ++++-------------- test/Token.t.sol | 6 +++--- 6 files changed, 25 insertions(+), 36 deletions(-) diff --git a/contracts/HtsSystemContract.sol b/contracts/HtsSystemContract.sol index 8c4f5b0b..e1456e5f 100644 --- a/contracts/HtsSystemContract.sol +++ b/contracts/HtsSystemContract.sol @@ -9,7 +9,7 @@ import {HederaResponseCodes} from "./HederaResponseCodes.sol"; address constant HTS_ADDRESS = address(0x167); -contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { +contract HtsSystemContract is IHederaTokenService { // All ERC20 properties are accessed with a `delegatecall` from the Token Proxy. // See `__redirectForToken` for more details. @@ -465,7 +465,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { address to = address(bytes20(msg.data[72:92])); uint256 amount = uint256(bytes32(msg.data[92:124])); _approve(from, to, amount); - emit ERC20Approval(from, to, amount); + emit IERC20Events.Approval(from, to, amount); return abi.encode(true); } if (selector == this.approveNFT.selector) { @@ -561,7 +561,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { uint256 amount = uint256(bytes32(msg.data[60:92])); address owner = msg.sender; _approve(owner, spender, amount); - emit ERC20Approval(owner, spender, amount); + emit IERC20Events.Approval(owner, spender, amount); return abi.encode(true); } return _redirectForHRC719(selector); @@ -735,7 +735,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { require(from != address(0), "hts: invalid sender"); require(to != address(0), "hts: invalid receiver"); _update(from, to, amount); - emit ERC20Transfer(from, to, amount); + emit IERC20Events.Transfer(from, to, amount); } function _transferNFT(address sender, address from, address to, uint256 serialId) private { @@ -763,7 +763,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { // Set the new owner assembly { sstore(slot, to) } - emit ERC721Transfer(from, to, serialId); + emit IERC721Events.Transfer(from, to, serialId); } function _update(address from, address to, uint256 amount) public { @@ -808,8 +808,7 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { bytes32 slot = _getApprovedSlot(uint32(serialId)); address newApproved = isApproved ? spender : address(0); assembly { sstore(slot, newApproved) } - - emit ERC721Approval(owner, spender, serialId); + emit IERC721Events.Approval(owner, spender, serialId); } /** @@ -830,6 +829,6 @@ contract HtsSystemContract is IHederaTokenService, IERC20Events, IERC721Events { require(operator != address(0) && operator != sender, "setApprovalForAll: invalid operator"); bytes32 slot = _isApprovedForAllSlot(sender, operator); assembly { sstore(slot, approved) } - emit ERC721ApprovalForAll(sender, operator, approved); + emit IERC721Events.ApprovalForAll(sender, operator, approved); } } diff --git a/contracts/IERC20.sol b/contracts/IERC20.sol index ad36c972..bf75c95d 100644 --- a/contracts/IERC20.sol +++ b/contracts/IERC20.sol @@ -13,13 +13,13 @@ interface IERC20Events { * * Note that `value` may be zero. */ - event ERC20Transfer(address indexed from, address indexed to, uint256 amount); + event Transfer(address indexed from, address indexed to, uint256 amount); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ - event ERC20Approval(address indexed owner, address indexed spender, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); } /** diff --git a/contracts/IERC721.sol b/contracts/IERC721.sol index 3f2f9069..f9c3ef6d 100644 --- a/contracts/IERC721.sol +++ b/contracts/IERC721.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.0; * See https://ethereum.org/en/developers/docs/standards/tokens/erc-721/#events for more information. */ interface IERC721Events { - event ERC721Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event ERC721Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); - event ERC721ApprovalForAll(address indexed owner, address indexed operator, bool approved); + event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } /** diff --git a/test/ERC721.t.sol b/test/ERC721.t.sol index e6272286..639f51bc 100644 --- a/test/ERC721.t.sol +++ b/test/ERC721.t.sol @@ -3,11 +3,11 @@ pragma solidity ^0.8.0; import {HtsSystemContract} from "../contracts/HtsSystemContract.sol"; import {Test, console} from "forge-std/Test.sol"; -import {IERC721, IERC721Events} from "../contracts/IERC721.sol"; -import {IERC20Events} from "../contracts/IERC20.sol"; +import {IERC721} from "../contracts/IERC721.sol"; +import {IERC721Events} from "../contracts/IERC721.sol"; import {TestSetup} from "./lib/TestSetup.sol"; -contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { +contract ERC721TokenTest is Test, TestSetup { function setUp() external { setUpMockStorageForNonFork(); @@ -39,7 +39,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit ERC721Transfer(CFNFTFF_TREASURY, to, tokenId); + emit IERC721Events.Transfer(CFNFTFF_TREASURY, to, tokenId); IERC721(CFNFTFF).transferFrom(CFNFTFF_TREASURY, to, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(tokenId), to); @@ -50,7 +50,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit ERC721Approval(CFNFTFF_TREASURY, spender, tokenId); + emit IERC721Events.Approval(CFNFTFF_TREASURY, spender, tokenId); IERC721(CFNFTFF).approve(spender, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).getApproved(tokenId), spender); @@ -60,7 +60,7 @@ contract ERC721TokenTest is Test, TestSetup, IERC721Events, IERC20Events { address operator = makeAddr("operator"); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit ERC721ApprovalForAll(CFNFTFF_TREASURY, operator, true); + emit IERC721Events.ApprovalForAll(CFNFTFF_TREASURY, operator, true); IERC721(CFNFTFF).setApprovalForAll(operator, true); assertTrue(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); } diff --git a/test/HTS.t.sol b/test/HTS.t.sol index 36c5dfc2..704fa8e5 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -631,7 +631,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.ERC20Transfer(owner, to, amount); + emit IERC20Events.Transfer(owner, to, amount); IHederaTokenService(HTS_ADDRESS).transferToken(USDC, owner, to, int64(int256(amount))); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -650,7 +650,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.ERC20Transfer(owner, to, amount); + emit IERC20Events.Transfer(owner, to, amount); IHederaTokenService(HTS_ADDRESS).transferFrom(USDC, owner, to, amount); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -674,8 +674,6 @@ contract HTSTest is Test, TestSetup { address to = makeAddr("recipient"); uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); - vm.expectEmit(CFNFTFF); - emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, CFNFTFF_TREASURY, to, int64(int256(serialId))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -685,8 +683,6 @@ contract HTSTest is Test, TestSetup { address to = makeAddr("recipient"); uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); - vm.expectEmit(CFNFTFF); - emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferFromNFT(CFNFTFF, CFNFTFF_TREASURY, to, serialId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -707,7 +703,7 @@ contract HTSTest is Test, TestSetup { vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.ERC20Transfer(owner, to[0], amount); + emit IERC20Events.Transfer(owner, to[0], amount); IHederaTokenService(HTS_ADDRESS).transferTokens(USDC, to, amounts); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -758,8 +754,6 @@ contract HTSTest is Test, TestSetup { address[] memory to = new address[](1); to[0] = makeAddr("recipient"); vm.startPrank(CFNFTFF_TREASURY); - vm.expectEmit(CFNFTFF); - emit IERC721Events.ERC721Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, from[0], to[0], int64(int256(serialId[0]))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId[0]), to[0]); @@ -799,8 +793,6 @@ contract HTSTest is Test, TestSetup { address newSpender = makeAddr("NEW_SPENDER"); assertNotEq(IERC721(token).getApproved(1), newSpender); vm.prank(CFNFTFF_TREASURY); - vm.expectEmit(token); - emit IERC721Events.ERC721Approval(CFNFTFF_TREASURY, newSpender, 1); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approveNFT(token, newSpender, 1); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC721(token).getApproved(1), newSpender); @@ -813,7 +805,7 @@ contract HTSTest is Test, TestSetup { assertEq(IERC20(USDC).allowance(owner, spender), 0); vm.prank(owner); vm.expectEmit(USDC); - emit IERC20Events.ERC20Approval(owner, spender, amount); + emit IERC20Events.Approval(owner, spender, amount); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approve(USDC, spender, amount); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC20(USDC).allowance(owner, spender), amount); @@ -823,8 +815,6 @@ contract HTSTest is Test, TestSetup { address operator = makeAddr("operator"); assertFalse(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); vm.prank(CFNFTFF_TREASURY); - vm.expectEmit(CFNFTFF); - emit IERC721Events.ERC721ApprovalForAll(CFNFTFF_TREASURY, operator, true); int64 setApprovalForAllResponseCode = HtsSystemContract(HTS_ADDRESS) .setApprovalForAll(CFNFTFF, operator, true); assertEq(setApprovalForAllResponseCode, HederaResponseCodes.SUCCESS); diff --git a/test/Token.t.sol b/test/Token.t.sol index bf50d996..93ddbceb 100644 --- a/test/Token.t.sol +++ b/test/Token.t.sol @@ -114,7 +114,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(owner); vm.expectEmit(USDC); - emit ERC20Approval(owner, spender, amount); + emit Approval(owner, spender, amount); IERC20(USDC).approve(spender, amount); assertEq(IERC20(USDC).allowance(owner, spender), amount); @@ -142,7 +142,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(owner); // https://book.getfoundry.sh/cheatcodes/prank vm.expectEmit(USDC); - emit ERC20Transfer(owner, to, amount); + emit Transfer(owner, to, amount); IERC20(USDC).transfer(to, amount); assertEq(IERC20(USDC).balanceOf(owner), balanceOfOwner - amount); @@ -186,7 +186,7 @@ contract TokenTest is Test, TestSetup, IERC20Events { vm.prank(bob); vm.expectEmit(USDC); - emit ERC20Transfer(alice, charlie, transferAmount); + emit Transfer(alice, charlie, transferAmount); IERC20(USDC).transferFrom(alice, charlie, transferAmount); assertEq(IERC20(USDC).balanceOf(alice), 54_300000 - 4_000000); From 666f83bc8acab22bd1f0a07466742fc22b14e3d8 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 23 Jan 2025 10:30:45 +0100 Subject: [PATCH 3/6] feat: reverting not needed changes (#194) Signed-off-by: Mariusz Jasuwienas --- contracts/HtsSystemContract.sol | 2 +- test/ERC721.t.sol | 3 +-- test/HTS.t.sol | 10 ++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/contracts/HtsSystemContract.sol b/contracts/HtsSystemContract.sol index e1456e5f..06f5247a 100644 --- a/contracts/HtsSystemContract.sol +++ b/contracts/HtsSystemContract.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; import {IERC20Events, IERC20} from "./IERC20.sol"; -import {IERC721Events, IERC721} from "./IERC721.sol"; +import {IERC721, IERC721Events} from "./IERC721.sol"; import {IHRC719} from "./IHRC719.sol"; import {IHederaTokenService} from "./IHederaTokenService.sol"; import {HederaResponseCodes} from "./HederaResponseCodes.sol"; diff --git a/test/ERC721.t.sol b/test/ERC721.t.sol index 639f51bc..e501dffc 100644 --- a/test/ERC721.t.sol +++ b/test/ERC721.t.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.0; import {HtsSystemContract} from "../contracts/HtsSystemContract.sol"; import {Test, console} from "forge-std/Test.sol"; -import {IERC721} from "../contracts/IERC721.sol"; -import {IERC721Events} from "../contracts/IERC721.sol"; +import {IERC721, IERC721Events} from "../contracts/IERC721.sol"; import {TestSetup} from "./lib/TestSetup.sol"; contract ERC721TokenTest is Test, TestSetup { diff --git a/test/HTS.t.sol b/test/HTS.t.sol index 704fa8e5..5b54e04d 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -674,6 +674,8 @@ contract HTSTest is Test, TestSetup { address to = makeAddr("recipient"); uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); + vm.expectEmit(CFNFTFF); + emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, CFNFTFF_TREASURY, to, int64(int256(serialId))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -683,6 +685,8 @@ contract HTSTest is Test, TestSetup { address to = makeAddr("recipient"); uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); + vm.expectEmit(CFNFTFF); + emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferFromNFT(CFNFTFF, CFNFTFF_TREASURY, to, serialId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -754,6 +758,8 @@ contract HTSTest is Test, TestSetup { address[] memory to = new address[](1); to[0] = makeAddr("recipient"); vm.startPrank(CFNFTFF_TREASURY); + vm.expectEmit(CFNFTFF); + emit IERC20Events.Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, from[0], to[0], int64(int256(serialId[0]))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId[0]), to[0]); @@ -793,6 +799,8 @@ contract HTSTest is Test, TestSetup { address newSpender = makeAddr("NEW_SPENDER"); assertNotEq(IERC721(token).getApproved(1), newSpender); vm.prank(CFNFTFF_TREASURY); + vm.expectEmit(token); + emit IERC20Events.Approval(CFNFTFF_TREASURY, newSpender, 1); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approveNFT(token, newSpender, 1); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC721(token).getApproved(1), newSpender); @@ -815,6 +823,8 @@ contract HTSTest is Test, TestSetup { address operator = makeAddr("operator"); assertFalse(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); vm.prank(CFNFTFF_TREASURY); + vm.expectEmit(CFNFTFF); + emit IERC721Events.ApprovalForAll(CFNFTFF_TREASURY, operator, true); int64 setApprovalForAllResponseCode = HtsSystemContract(HTS_ADDRESS) .setApprovalForAll(CFNFTFF, operator, true); assertEq(setApprovalForAllResponseCode, HederaResponseCodes.SUCCESS); From f36c65074b50407bc0db8eb842f05cf3034c7e4d Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Thu, 23 Jan 2025 10:35:32 +0100 Subject: [PATCH 4/6] test: checking for correct event in tests (#194) Signed-off-by: Mariusz Jasuwienas --- test/HTS.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/HTS.t.sol b/test/HTS.t.sol index 5b54e04d..41bb952f 100644 --- a/test/HTS.t.sol +++ b/test/HTS.t.sol @@ -675,7 +675,7 @@ contract HTSTest is Test, TestSetup { uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); + emit IERC721Events.Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, CFNFTFF_TREASURY, to, int64(int256(serialId))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -686,7 +686,7 @@ contract HTSTest is Test, TestSetup { uint256 serialId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to, serialId); + emit IERC721Events.Transfer(CFNFTFF_TREASURY, to, serialId); IHederaTokenService(HTS_ADDRESS).transferFromNFT(CFNFTFF, CFNFTFF_TREASURY, to, serialId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId), to); @@ -759,7 +759,7 @@ contract HTSTest is Test, TestSetup { to[0] = makeAddr("recipient"); vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC20Events.Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); + emit IERC721Events.Transfer(CFNFTFF_TREASURY, to[0], serialId[0]); IHederaTokenService(HTS_ADDRESS).transferNFT(CFNFTFF, from[0], to[0], int64(int256(serialId[0]))); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(serialId[0]), to[0]); @@ -800,7 +800,7 @@ contract HTSTest is Test, TestSetup { assertNotEq(IERC721(token).getApproved(1), newSpender); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(token); - emit IERC20Events.Approval(CFNFTFF_TREASURY, newSpender, 1); + emit IERC721Events.Approval(CFNFTFF_TREASURY, newSpender, 1); int64 responseCodeApprove = HtsSystemContract(HTS_ADDRESS).approveNFT(token, newSpender, 1); assertEq(responseCodeApprove, HederaResponseCodes.SUCCESS); assertEq(IERC721(token).getApproved(1), newSpender); From 4685c22540d9f40aadfb2b201aa124a447ede56b Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Fri, 24 Jan 2025 14:15:35 +0100 Subject: [PATCH 5/6] docs: better descriptions for nft events (#194) Signed-off-by: Mariusz Jasuwienas --- README.md | 76 +++++++++++++++++++++++++++++-------------- contracts/IERC721.sol | 25 ++++++++++++++ 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index f2997d45..0400160a 100644 --- a/README.md +++ b/README.md @@ -367,26 +367,27 @@ The following methods and events are applicable to Fungible Tokens. The following methods and events are applicable to Non-Fungible Tokens. - - -| Function | Comment | -| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `approve(address _approved, uint256 _tokenId) payable` | Change or reaffirm the approved address for an NFT. The zero address indicates there is no approved address. Throws unless `msg.sender` is the current NFT owner, or an authorized operator of the current owner. | -| `balanceOf(address _owner) view` | Count all NFTs assigned to an owner. NFTs assigned to the zero address are considered invalid, and this function throws for queries about the zero address. | -| `getApproved(uint256 _tokenId) view` | Get the approved address for a single NFT. Throws if `_tokenId` is not a valid NFT. | -| `isApprovedForAll(address _owner, address _operator) view` | Query if an address is an authorized operator for another address | -| `ownerOf(uint256 _tokenId) view` | Find the owner of an NFT. NFTs assigned to zero address are considered invalid, and queries about them do throw. | -| `safeTransferFrom(address _from, address _to, uint256 _tokenId) payable` | Transfers the ownership of an NFT from one address to another address. This works identically to the other function with an extra data parameter, except this function just sets data to "". | -| `safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) payable` | Transfers the ownership of an NFT from one address to another address. Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT. Throws if `_from` is not the current owner. Throws if `_to` is the zero address. Throws if `_tokenId` is not a valid NFT. When transfer is complete, this function checks if `_to` is a smart contract (code size > 0). If so, it calls `onERC721Received` on `_to` and throws if the return value is not `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. | -| `setApprovalForAll(address _operator, bool _approved)` | Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets. Emits the ApprovalForAll event. The contract MUST allow multiple operators per owner. | -| `supportsInterface(bytes4 interfaceID) view` | Query if a contract implements an interface. Interface identification is specified in ERC-165. This function uses less than 30,000 gas. | -| `transferFrom(address _from, address _to, uint256 _tokenId) payable` | Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE THEY MAY BE PERMANENTLY LOST. Throws unless `msg.sender` is the current owner, an authorized operator, or the approved address for this NFT. Throws if `_from` is not the current owner. Throws if `_to` is the zero address. Throws if `_tokenId` is not a valid NFT. | - -| Event | Comment | -| --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId)` | This emits when the approved address for an NFT is changed or reaffirmed. The zero address indicates there is no approved address. When a Transfer event emits, this also indicates that the approved address for that NFT (if any) is reset to none. | -| `ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved)` | This emits when an operator is enabled or disabled for an owner. The operator can manage all NFTs of the owner. | -| `Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId)` | This emits when ownership of any NFT changes by any mechanism. This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0). Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. At the time of any transfer, the approved address for that NFT (if any) is reset to none. | + + +| Function | Comment | +| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `approve(address spender, uint256 serialId) payable` | 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. | +| `balanceOf(address owner) view` | Returns the number of tokens in `owner`'s account. | +| `getApproved(uint256 serialId) view` | Returns the account approved for `serialId` token. Requirements: - `serialId` must exist. | +| `isApprovedForAll(address owner, address operator) view` | Returns if the `operator` is allowed to manage all of the assets of `owner`. See {setApprovalForAll} | +| `name() view` | Returns the token collection name. | +| `ownerOf(uint256 serialId) view` | Returns the owner of the `serialId` token. Requirements: - `serialId` must exist. This value changes when {approve} or {transferFrom} are called. | +| `setApprovalForAll(address operator, bool approved)` | 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. | +| `symbol() view` | Returns the token collection symbol. | +| `tokenURI(uint256 serialId) view` | Returns the Uniform Resource Identifier (URI) for `serialId` token. | +| `totalSupply() view` | Returns the total amount of tokens stored by the contract. | +| `transferFrom(address sender, address recipient, uint256 serialId) payable` | 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. | + +| Event | Comment | +| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)` | Emitted when the approved address for an NFT is changed or reaffirmed from {from} to {to} address. The zero {to} address indicates there will be no approved address. Additionally the approved address for that NFT (if any) is reset to none. | +| `ApprovalForAll(address indexed owner, address indexed operator, bool approved)` | Emitted when an operator {operator} is enabled or disabled {approved} for an owner {owner}. The operator {operator} can than manage all NFTs of the owner {owner}. | +| `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` | Emitted when ownership of any NFT changes by any mechanism. This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0). Otherwise, it indicates that the token with ID {tokenId} was transferred from {from} to {to}, where {from} represents the previous owner of the token, not the approved spender. Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. At the time of any transfer, the approved address for that NFT (if any) is reset to none. | @@ -408,11 +409,36 @@ The following methods can be invoked on the Hedera Token Service contract locate -| Function | Comment | -| --------------------------------------------------------------- | -------------------------------------------------------------- | -| `burnToken(address token, int64 amount, int64[] serialNumbers)` | Burns an amount of the token from the defined treasury account | -| `getTokenInfo(address token)` | Query token info | -| `mintToken(address token, int64 amount, bytes[] metadata)` | Mints an amount of the token to the defined treasury account | +| Function | Comment | +| ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `allowance(address token, address owner, address spender)` | Returns the amount which spender is still allowed to withdraw from owner. Only Applicable to Fungible Tokens | +| `approve(address token, address spender, uint256 amount)` | Allows spender to withdraw from your account multiple times, up to the value amount. If this function is called again it overwrites the current allowance with value. Only Applicable to Fungible Tokens | +| `approveNFT(address token, address approved, uint256 serialNumber)` | Allow or reaffirm the approved address to transfer an NFT the approved address does not own. Only Applicable to NFT Tokens | +| `associateToken(address account, address token)` | Single-token variant of associateTokens. Will be mapped to a single entry array call of associateTokens | +| `associateTokens(address account, address[] tokens)` | Associates the provided account with the provided tokens. Must be signed by the provided Account's key or called from the accounts contract key If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED. If an association between the provided account and any of the tokens already exists, the transaction will resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT. If the provided account's associations count exceed the constraint of maximum token associations per account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED. On success, associations between the provided account and tokens are made and the account is ready to interact with the tokens. | +| `burnToken(address token, int64 amount, int64[] serialNumbers)` | Burns an amount of the token from the defined treasury account | +| `dissociateToken(address account, address token)` | Single-token variant of dissociateTokens. Will be mapped to a single entry array call of dissociateTokens | +| `dissociateTokens(address account, address[] tokens)` | Dissociates the provided account with the provided tokens. Must be signed by the provided Account's key. If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID. If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED. If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF. If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED. If an association between the provided account and any of the tokens does not exist, the transaction will resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT. If a token has not been deleted and has not expired, and the user has a nonzero balance, the transaction will resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES. If a fungible token has expired, the user can disassociate even if their token balance is not zero. If a non fungible token has expired, the user can not disassociate if their token balance is not zero. The transaction will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES. On success, associations between the provided account and tokens are removed. | +| `getApproved(address token, uint256 serialNumber)` | Get the approved address for a single NFT Only Applicable to NFT Tokens | +| `getFungibleTokenInfo(address token)` | Query fungible token info | +| `getNonFungibleTokenInfo(address token, int64 serialNumber)` | Query non fungible token info | +| `getTokenCustomFees(address token)` | Query token custom fees | +| `getTokenDefaultFreezeStatus(address token)` | Query token default freeze status | +| `getTokenDefaultKycStatus(address token)` | Query token default kyc status | +| `getTokenExpiryInfo(address token)` | Query token expiry info | +| `getTokenInfo(address token)` | Query token info | +| `getTokenKey(address token, uint256 keyType)` | Query token KeyValue | +| `getTokenType(address token)` | Query to return the token type for a given address | +| `isApprovedForAll(address token, address owner, address operator)` | Query if an address is an authorized operator for another address Only Applicable to NFT Tokens | +| `isToken(address token)` | Query if valid token found for the given address | +| `mintToken(address token, int64 amount, bytes[] metadata)` | Mints an amount of the token to the defined treasury account | +| `setApprovalForAll(address token, address operator, bool approved)` | Enable or disable approval for a third party ("operator") to manage all of `msg.sender`'s assets | +| `transferFrom(address token, address from, address to, uint256 amount)` | Only applicable to fungible tokens | +| `transferFromNFT(address token, address from, address to, uint256 serialNumber)` | Transfers `serialNumber` of `token` from `from` to `to` using the allowance mechanism. Only applicable to NFT tokens | +| `transferNFT(address token, address sender, address recipient, int64 serialNumber)` | Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list, where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending (positive amount) or receiving (negative amount) | +| `transferNFTs(address token, address[] sender, address[] receiver, int64[] serialNumber)` | Initiates a Non-Fungable Token Transfer | +| `transferToken(address token, address sender, address recipient, int64 amount)` | Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list, where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending (positive amount) or receiving (negative amount) | +| `transferTokens(address token, address[] accountId, int64[] amount)` | Initiates a Fungible Token Transfer | diff --git a/contracts/IERC721.sol b/contracts/IERC721.sol index f9c3ef6d..109acfc4 100644 --- a/contracts/IERC721.sol +++ b/contracts/IERC721.sol @@ -2,11 +2,34 @@ pragma solidity ^0.8.0; /** + * These events should be emitted by `transfer|transferFrom`, `approve` and `setApprovalForAll` respectively. + * * See https://ethereum.org/en/developers/docs/standards/tokens/erc-721/#events for more information. */ interface IERC721Events { + /** + * @dev Emitted when ownership of any NFT changes by any mechanism. + * This event emits when NFTs are created (`from` == 0) and destroyed (`to` == 0). + * Otherwise, it indicates that the token with ID {tokenId} was transferred from {from} to {to}, + * where {from} represents the previous owner of the token, not the approved spender. + * + * Exception: during contract creation, any number of NFTs may be created and assigned without emitting Transfer. + * + * At the time of any transfer, the approved address for that NFT (if any) is reset to none. + */ event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); + + /** + * @dev Emitted when the approved address for an NFT is changed or reaffirmed from {from} to {to} address. + * The zero {to} address indicates there will be no approved address. + * Additionally the approved address for that NFT (if any) is reset to none. + */ event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId); + + /** + * @dev Emitted when an operator {operator} is enabled or disabled {approved} + * for an owner {owner}. The operator {operator} can than manage all NFTs of the owner {owner}. + */ event ApprovalForAll(address indexed owner, address indexed operator, bool approved); } @@ -47,6 +70,8 @@ interface IERC721 { * * Requirements: * - `serialId` must exist. + * + * This value changes when {approve} or {transferFrom} are called. */ function ownerOf(uint256 serialId) external view returns (address); From d298dab35a28dbf5befdc45b4fd68594f641356e Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Sun, 26 Jan 2025 15:48:27 +0100 Subject: [PATCH 6/6] feat: making erc721 tests consistent with erc20 alternative (#194) Signed-off-by: Mariusz Jasuwienas --- test/ERC721.t.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/ERC721.t.sol b/test/ERC721.t.sol index e501dffc..ede9d963 100644 --- a/test/ERC721.t.sol +++ b/test/ERC721.t.sol @@ -6,7 +6,7 @@ import {Test, console} from "forge-std/Test.sol"; import {IERC721, IERC721Events} from "../contracts/IERC721.sol"; import {TestSetup} from "./lib/TestSetup.sol"; -contract ERC721TokenTest is Test, TestSetup { +contract ERC721TokenTest is Test, TestSetup, IERC721Events { function setUp() external { setUpMockStorageForNonFork(); @@ -38,7 +38,7 @@ contract ERC721TokenTest is Test, TestSetup { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC721Events.Transfer(CFNFTFF_TREASURY, to, tokenId); + emit Transfer(CFNFTFF_TREASURY, to, tokenId); IERC721(CFNFTFF).transferFrom(CFNFTFF_TREASURY, to, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).ownerOf(tokenId), to); @@ -49,7 +49,7 @@ contract ERC721TokenTest is Test, TestSetup { uint256 tokenId = 1; vm.startPrank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC721Events.Approval(CFNFTFF_TREASURY, spender, tokenId); + emit Approval(CFNFTFF_TREASURY, spender, tokenId); IERC721(CFNFTFF).approve(spender, tokenId); vm.stopPrank(); assertEq(IERC721(CFNFTFF).getApproved(tokenId), spender); @@ -59,7 +59,7 @@ contract ERC721TokenTest is Test, TestSetup { address operator = makeAddr("operator"); vm.prank(CFNFTFF_TREASURY); vm.expectEmit(CFNFTFF); - emit IERC721Events.ApprovalForAll(CFNFTFF_TREASURY, operator, true); + emit ApprovalForAll(CFNFTFF_TREASURY, operator, true); IERC721(CFNFTFF).setApprovalForAll(operator, true); assertTrue(IERC721(CFNFTFF).isApprovedForAll(CFNFTFF_TREASURY, operator)); }