From 7082e0bbc35481aa3191244a73117a5320117dc2 Mon Sep 17 00:00:00 2001 From: han Date: Wed, 22 Jan 2025 23:54:13 +0700 Subject: [PATCH] add test suite for sell and buy erc20 --- src/TokenDivider.sol | 3 + .../TokenDividerTest.audit.sellErc20.t.sol | 237 ++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 test/unit/TokenDividerTest.audit.sellErc20.t.sol diff --git a/src/TokenDivider.sol b/src/TokenDivider.sol index d9c9665..c67b6d4 100644 --- a/src/TokenDivider.sol +++ b/src/TokenDivider.sol @@ -239,6 +239,7 @@ contract TokenDivider is IERC721Receiver, Ownable { revert TokenDivider__AmountCantBeZero(); } // @audit: no check for 0 price? + // @audit-report: no check but it successfully transfer the given amount without transfering the given ether ERC20Info memory tokenInfo = nftToErc20Info[nftPegged]; if (balances[msg.sender][tokenInfo.erc20Address] < amount) { @@ -309,7 +310,9 @@ contract TokenDivider is IERC721Receiver, Ownable { // send "fee" to "owner"? // @audit: if buyer needs to pay price + seller fee. then seller is given price - seller fee. does fee is guarranted to be paid by buyer? + // @audit-report: yes it does. if the price is 0, then there is no value increase // @audit: who owner? + // @audit-report: the deployer (bool taxSuccess,) = payable(owner()).call{value: fee}(""); if (!taxSuccess) { diff --git a/test/unit/TokenDividerTest.audit.sellErc20.t.sol b/test/unit/TokenDividerTest.audit.sellErc20.t.sol new file mode 100644 index 0000000..69456b7 --- /dev/null +++ b/test/unit/TokenDividerTest.audit.sellErc20.t.sol @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import {Test} from 'forge-std/Test.sol'; +import {DeployTokenDivider} from 'script/DeployTokenDivider.s.sol'; +import {TokenDivider} from 'src/TokenDivider.sol'; +import {ERC721Mock} from '../mocks/ERC721Mock.sol'; +import {ERC20Mock} from '@openzeppelin/contracts/mocks/token/ERC20Mock.sol'; + +contract TokenDividerSellTest is Test { + DeployTokenDivider deployer; + TokenDivider tokenDivider; + ERC721Mock erc721Mock; + + address public USER = makeAddr("user"); + address public USER2 = makeAddr("user2"); + uint256 constant public STARTING_USER_BALANCE = 10e18; + uint256 constant public AMOUNT = 2e18; + uint256 constant public TOKEN_ID = 0; + + function setUp() public { + deployer = new DeployTokenDivider(); + tokenDivider = deployer.run(); + + erc721Mock = new ERC721Mock(); + + erc721Mock.mint(USER); + + // divide the ERC721 and mint ERC20 + vm.startPrank(USER); + erc721Mock.approve(address(tokenDivider), TOKEN_ID); + tokenDivider.divideNft( + address(erc721Mock), + TOKEN_ID, + AMOUNT + ); + vm.stopPrank(); + } + + function test_assertSetUp() public { + erc721Mock.mint(USER); + ERC20Mock erc20Mock = ERC20Mock( + tokenDivider.getErc20InfoFromNft( + address(erc721Mock) + ).erc20Address + ); + + // assert if NFT already moved to tokenDivider + assertEq( + erc721Mock.ownerOf(TOKEN_ID), + address(tokenDivider) + ); + + // assert if USER has the ERC20 token + assertEq( + tokenDivider.getBalanceOf(USER, address(erc20Mock)), + AMOUNT + ); + } + + function test_sellErc20() public { + ERC20Mock erc20Mock = ERC20Mock( + tokenDivider.getErc20InfoFromNft( + address(erc721Mock) + ).erc20Address + ); + + uint256 sellAmount = 1; + vm.startPrank(USER); + erc20Mock.approve(address(tokenDivider), sellAmount); + tokenDivider.sellErc20( + address(erc721Mock), + 1, + sellAmount + ); + vm.stopPrank(); + + assertEq( + tokenDivider.getBalanceOf(USER, address(erc20Mock)), + AMOUNT - 1 + ); + } + + function test_sellErc20WithZeroPrice() public { + ERC20Mock erc20Mock = ERC20Mock( + tokenDivider.getErc20InfoFromNft( + address(erc721Mock) + ).erc20Address + ); + + uint256 sellAmount = 1e17; + uint256 sellPrice = 0; + vm.startPrank(USER); + erc20Mock.approve(address(tokenDivider), sellAmount); + tokenDivider.sellErc20( + address(erc721Mock), + sellPrice, + sellAmount + ); + vm.stopPrank(); + + assertEq( + tokenDivider.getBalanceOf(USER, address(erc20Mock)), + AMOUNT - sellAmount + ); + + assertEq( + sellPrice, + tokenDivider.getOrderPrice( + address(USER), + 0 // index of order price + ) + ); + } + + function test_sellAndBuyErc20() public { + ERC20Mock erc20Mock = ERC20Mock( + tokenDivider.getErc20InfoFromNft( + address(erc721Mock) + ).erc20Address + ); + + uint256 sellAmount = 1e17; + uint256 sellPrice = 1 ether; + vm.startPrank(USER); + erc20Mock.approve(address(tokenDivider), sellAmount); + tokenDivider.sellErc20( + address(erc721Mock), + sellPrice, + sellAmount + ); + vm.stopPrank(); + + assertEq( + tokenDivider.getBalanceOf(USER, address(erc20Mock)), + AMOUNT - sellAmount + ); + + uint256 buyPrice = sellPrice + ((sellPrice / 100) / 2); + vm.deal( + address(USER2), + buyPrice + ); + + uint256 contractInitialBalance = address( + tokenDivider.owner() + ).balance; + assertEq( + address(USER).balance, + 0 + ); + + vm.startPrank(USER2); + tokenDivider.buyOrder{value: buyPrice}( + 0, + address(USER) + ); + vm.stopPrank(); + + assertEq( + erc20Mock.balanceOf(address(USER2)), + sellAmount + ); + uint256 sellerFee = buyPrice - sellPrice; + assertEq( + address( + tokenDivider.owner() + ).balance - contractInitialBalance, + sellerFee * 2 + ); + assertEq( + address(USER).balance, + sellPrice - sellerFee + ); + } + + function test_sellAndBuyErc20With0Price() public { + ERC20Mock erc20Mock = ERC20Mock( + tokenDivider.getErc20InfoFromNft( + address(erc721Mock) + ).erc20Address + ); + + uint256 sellAmount = 1e17; + uint256 sellPrice = 0 ether; + vm.startPrank(USER); + erc20Mock.approve(address(tokenDivider), sellAmount); + tokenDivider.sellErc20( + address(erc721Mock), + sellPrice, + sellAmount + ); + vm.stopPrank(); + + assertEq( + tokenDivider.getBalanceOf(USER, address(erc20Mock)), + AMOUNT - sellAmount + ); + + uint256 buyPrice = sellPrice + ((sellPrice / 100) / 2); + vm.deal( + address(USER2), + buyPrice + ); + + uint256 contractInitialBalance = address( + tokenDivider.owner() + ).balance; + assertEq( + address(USER).balance, + 0 + ); + + vm.startPrank(USER2); + tokenDivider.buyOrder{value: buyPrice}( + 0, + address(USER) + ); + vm.stopPrank(); + + assertEq( + erc20Mock.balanceOf(address(USER2)), + sellAmount + ); + uint256 sellerFee = buyPrice - sellPrice; + assertEq( + address( + tokenDivider.owner() + ).balance - contractInitialBalance, + sellerFee * 2 + ); + assertEq( + address(USER).balance, + sellPrice - sellerFee + ); + } +}