additional tests added and extra mocks added

Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
Uncle Fatso 2025-05-17 16:07:39 +03:00
parent 1f3dd1be96
commit c3e0d54be4
Signed by: f4ts0
GPG Key ID: 565F4F2860226EBB
8 changed files with 682 additions and 1 deletions

57
src/mock/SigUtils.sol Normal file
View File

@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
contract SigUtils {
bytes32 internal DOMAIN_SEPARATOR;
constructor(bytes32 _DOMAIN_SEPARATOR) {
DOMAIN_SEPARATOR = _DOMAIN_SEPARATOR;
}
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
struct Permit {
address owner;
address spender;
uint256 value;
uint256 nonce;
uint256 deadline;
}
// computes the hash of a permit
function getStructHash(Permit memory _permit)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encode(
PERMIT_TYPEHASH,
_permit.owner,
_permit.spender,
_permit.value,
_permit.nonce,
_permit.deadline
)
);
}
// computes the hash of the fully encoded EIP-712 message for the domain, which can be used to recover the signer
function getTypedDataHash(Permit memory _permit)
public
view
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
getStructHash(_permit)
)
);
}
}

79
test/UniswapV2ERC20.t.sol Normal file
View File

@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
import {Test} from "forge-std/Test.sol";
import "../src/UniswapV2ERC20.sol";
import "./tokens/Permit.t.sol";
import "./tokens/Allowance.t.sol";
import "./tokens/Transfer.t.sol";
contract UniswapV2ERC20Mintable is UniswapV2ERC20 {
function mint(address to, uint256 value) public {
_mint(to, value);
}
}
contract UniswapV2ERC20Test is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTest {
UniswapV2ERC20Mintable uni;
address constant alice = 0x0000000000000000000000000000000000000001;
address constant bob = 0x0000000000000000000000000000000000000002;
uint256 constant amount = 1e23;
uint256 constant maxAmount = type(uint256).max;
function setUp() public {
uni = new UniswapV2ERC20Mintable();
initializePermit(address(uni), amount, maxAmount);
initializeAllowance(alice, bob, address(uni), amount, maxAmount, amount);
initializeTransfer(alice, bob, address(uni), amount, 0);
}
function testNameIsCorrect() public view {
assertEq(uni.name(), "Uniswap V2");
}
function testSymbolIsCorrect() public view {
assertEq(uni.symbol(), "UNI-V2");
}
function testNumberOfDecimalslIsCorrect() public view {
assertEq(uni.decimals(), 18);
}
function testInitialSupplyIsZero() public view {
assertEq(uni.totalSupply(), 0);
}
function testDomainSeparatorIsCorrect() public view {
assertEq(uni.DOMAIN_SEPARATOR(), keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(uni.name())),
keccak256(bytes("1")),
block.chainid,
address(uni)
)
));
}
function testPermitTypeHashIsCorrect() public view {
assertEq(
uni.PERMIT_TYPEHASH(),
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
);
}
function _mintTransferTokens(address who, uint256 value) internal override {
uni.mint(who, value);
}
function _mintAllowanceTokens(address who, uint256 value) internal override {
uni.mint(who, value);
}
function _mintPermitTokens(address who, uint256 value) internal override {
uni.mint(who, value);
}
}

View File

@ -38,6 +38,19 @@ contract UniswapV2FactoryTest is Test {
assertEq(UniswapV2Pair(tokenPair).token1(), _token1);
}
function testCreatePairReverse() public {
address tokenPair = factory.createPair(address(token1), address(token0));
(address _token0, address _token1) = UniswapV2Library.sortTokens(
address(token0),
address(token1)
);
assertEq(factory.allPairsLength(), 1);
assertEq(factory.getPair(address(token0), address(token1)), tokenPair);
assertEq(UniswapV2Pair(tokenPair).token0(), _token0);
assertEq(UniswapV2Pair(tokenPair).token1(), _token1);
}
function testCreatePairMultipleTokens() public {
address tokenPair0 = factory.createPair(address(token0), address(token1));
address tokenPair1 = factory.createPair(address(token2), address(token3));

View File

@ -45,7 +45,6 @@ contract TestUniswapV2Pair is Test {
factory = new UniswapV2Factory(address(0));
address tokenAddress = factory.createPair(address(token0), address(token1));
pair = UniswapV2Pair(tokenAddress);
// pair.initialize(address(token0), address(token1));
token0.mint(address(this), 10 ether);
token1.mint(address(this), 10 ether);

View File

@ -22,6 +22,9 @@ contract TestUniswapV2Router is Test {
WETH9 public weth;
fallback() external payable {}
receive() external payable {}
function setUp() public {
weth = new WETH9();
factory = new UniswapV2Factory(address(0));
@ -34,6 +37,34 @@ contract TestUniswapV2Router is Test {
token1.mint(address(this), 10 ether);
}
function testAddLiquidityEthPairFor() public {
token0.approve(address(router), 10 ether);
(address _token0, address _token1) = UniswapV2Library.sortTokens(
address(token0),
address(weth)
);
address pair = UniswapV2Library.pairFor(
address(factory),
_token0,
_token1
);
(, , uint256 liquidity) = router.addLiquidityETH{value: 1 ether}(
address(token0),
1 ether,
1 ether,
1 ether,
address(this),
block.timestamp + 1
);
assertEq(liquidity, 1 ether - UniswapV2Pair(pair).MINIMUM_LIQUIDITY());
assertEq(factory.getPair(address(token0), address(weth)), pair);
assertEq(weth.balanceOf(address(pair)), 1 ether);
assertEq(token0.balanceOf(address(pair)), 1 ether);
}
function testAddLiquidityPairFor() public {
token0.approve(address(router), 10 ether);
token1.approve(address(router), 10 ether);
@ -252,6 +283,44 @@ contract TestUniswapV2Router is Test {
);
}
function testRemoveLiquidityEth() public {
token0.approve(address(router), 10 ether);
weth.approve(address(router), 10 ether);
(uint256 amount0, uint256 amount1, uint256 liquidity) = router.addLiquidityETH{value: 1 ether}(
address(token0),
1 ether,
1 ether,
1 ether,
address(this),
block.timestamp + 1
);
uint256 prevAmount0 = token0.balanceOf(address(this));
uint256 prevAmount1 = address(this).balance;
address pair = factory.getPair(address(token0), address(weth));
assertEq(IERC20(pair).balanceOf(address(this)), liquidity);
IERC20(pair).approve(address(router), liquidity);
router.removeLiquidityETH(
address(token0),
liquidity,
0,
0,
address(this),
block.timestamp + 1
);
UniswapV2Pair(pair).skim(address(this));
assertEq(IERC20(pair).balanceOf(address(this)), 0);
assertEq(token0.balanceOf(address(this)), prevAmount0 + amount0 - UniswapV2Pair(pair).MINIMUM_LIQUIDITY());
assertEq(address(this).balance, prevAmount1 + amount1 - UniswapV2Pair(pair).MINIMUM_LIQUIDITY());
assertEq(token0.balanceOf(pair), UniswapV2Pair(pair).MINIMUM_LIQUIDITY());
assertEq(weth.balanceOf(address(pair)), UniswapV2Pair(pair).MINIMUM_LIQUIDITY());
}
function testRemoveLiquidity() public {
token0.approve(address(router), 1 ether);
token1.approve(address(router), 1 ether);

View File

@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
import {Test} from "forge-std/Test.sol";
import "@openzeppelin-contracts/token/ERC20/ERC20.sol";
abstract contract ERC20AllowanceTest is Test {
ERC20 tokenAllowance;
uint256 amountAllowance;
uint256 maxAmountAllowance;
uint256 maxRealAmountAllowance;
address aliceAllowance;
address bobAllowance;
function initializeAllowance(
address alice,
address bob,
address token,
uint256 amount,
uint256 maxAmount,
uint256 maxRealAmount
) public {
tokenAllowance = ERC20(token);
amountAllowance = amount;
maxAmountAllowance = maxAmount;
maxRealAmountAllowance = maxRealAmount;
aliceAllowance = alice;
bobAllowance = bob;
}
function test_allowance_couldApproveFunds() public {
_mintAllowanceTokens(aliceAllowance, amountAllowance);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), 0);
vm.prank(aliceAllowance);
tokenAllowance.approve(bobAllowance, amountAllowance);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
}
function test_allowance_transferFromDecreaseAllowance() public {
_mintAllowanceTokens(aliceAllowance, amountAllowance);
vm.prank(aliceAllowance);
tokenAllowance.approve(bobAllowance, amountAllowance);
assertEq(tokenAllowance.balanceOf(aliceAllowance), amountAllowance);
assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
vm.prank(bobAllowance);
bool success = tokenAllowance.transferFrom(aliceAllowance, bobAllowance, amountAllowance);
assertEq(success, true);
assertEq(tokenAllowance.balanceOf(aliceAllowance), 0);
assertEq(tokenAllowance.balanceOf(bobAllowance), amountAllowance);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), 0);
}
function test_allowance_couldNotTransferFromIfNotEnoughFunds() public {
_mintAllowanceTokens(aliceAllowance, amountAllowance);
vm.prank(aliceAllowance);
tokenAllowance.approve(bobAllowance, maxAmountAllowance);
assertEq(tokenAllowance.balanceOf(aliceAllowance), amountAllowance);
assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), maxAmountAllowance);
vm.expectRevert();
vm.prank(bobAllowance);
tokenAllowance.transferFrom(aliceAllowance, bobAllowance, maxAmountAllowance);
}
function test_allowance_couldNotTransferFromIfNotEnoughAllowance() public {
_mintAllowanceTokens(aliceAllowance, maxRealAmountAllowance);
vm.prank(aliceAllowance);
tokenAllowance.approve(bobAllowance, amountAllowance);
assertEq(tokenAllowance.balanceOf(aliceAllowance), maxRealAmountAllowance);
assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
vm.expectRevert();
vm.prank(bobAllowance);
tokenAllowance.transferFrom(aliceAllowance, bobAllowance, maxAmountAllowance);
}
function _mintAllowanceTokens(address who, uint256 value) internal virtual;
}

306
test/tokens/Permit.t.sol Normal file
View File

@ -0,0 +1,306 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
import {Test} from "forge-std/Test.sol";
import "../../src/mock/SigUtils.sol";
import "@openzeppelin-contracts/token/ERC20/extensions/ERC20Permit.sol";
abstract contract ERC20PermitTest is Test {
SigUtils sigUtils;
ERC20Permit permitToken;
address owner;
address spender;
uint256 permitAmount;
uint256 maxPermitAmount;
uint256 constant ownerPrivateKey = 0xA11CE;
uint256 constant spenderPrivateKey = 0xB0B;
function initializePermit(
address token,
uint256 amount,
uint256 maxAmount
) public {
permitAmount = amount;
maxPermitAmount = maxAmount;
permitToken = ERC20Permit(token);
sigUtils = new SigUtils(permitToken.DOMAIN_SEPARATOR());
owner = vm.addr(ownerPrivateKey);
spender = vm.addr(spenderPrivateKey);
}
function test_permit_initialNonceIsZero() public view {
assertEq(permitToken.nonces(owner), 0);
assertEq(permitToken.nonces(spender), 0);
}
function test_permit_acceptsOwnerSignature() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
assertEq(permitToken.allowance(owner, spender), permitAmount);
assertEq(permitToken.nonces(owner), 1);
}
function test_permit_expiredPermit() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: permitToken.nonces(owner),
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
vm.warp(1 days + 1 seconds); // fast forward one second past the deadline
vm.expectRevert();
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
}
function test_permit_invalidSigner() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: permitToken.nonces(owner),
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(spenderPrivateKey, digest); // spender signs owner's approval
vm.expectRevert();
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
}
function test_permit_invalidNonce() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: 1, // owner nonce stored on-chain is 0
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
vm.expectRevert();
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
}
function test_permit_signatureReplay() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
vm.expectRevert();
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
}
function test_permit_transferFromLimitedPermit() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: permitAmount,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
vm.prank(spender);
permitToken.transferFrom(owner, spender, permitAmount);
assertEq(permitToken.balanceOf(owner), 0);
assertEq(permitToken.balanceOf(spender), permitAmount);
assertEq(permitToken.allowance(owner, spender), 0);
}
function test_permit_transferFromMaxPermit() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: maxPermitAmount,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
vm.prank(spender);
permitToken.transferFrom(owner, spender, permitAmount);
assertEq(permitToken.balanceOf(owner), 0);
assertEq(permitToken.balanceOf(spender), permitAmount);
assertEq(permitToken.allowance(owner, spender), maxPermitAmount);
}
function test_permit_invalidAllowance() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: 1,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
vm.expectRevert();
vm.prank(spender);
permitToken.transferFrom(owner, spender, permitAmount);
}
function test_permit_ivnalidBalance() public {
_mintPermitTokens(owner, permitAmount);
SigUtils.Permit memory permit = SigUtils.Permit({
owner: owner,
spender: spender,
value: 2,
nonce: 0,
deadline: 1 days
});
bytes32 digest = sigUtils.getTypedDataHash(permit);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPrivateKey, digest);
permitToken.permit(
permit.owner,
permit.spender,
permit.value,
permit.deadline,
v,
r,
s
);
vm.expectRevert();
vm.prank(spender);
permitToken.transferFrom(owner, spender, permitAmount);
}
function _mintPermitTokens(address who, uint256 permitAmount) internal virtual;
}

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.20;
import {Test} from "forge-std/Test.sol";
import "@openzeppelin-contracts/token/ERC20/ERC20.sol";
abstract contract ERC20TransferTest is Test {
ERC20 tokenTransfer;
uint256 amountTransfer;
uint256 totalSupplyTransfer;
address aliceTransfer;
address bobTransfer;
function initializeTransfer(
address alice,
address bob,
address token,
uint256 amount,
uint256 totalSupply
) public {
tokenTransfer = ERC20(token);
amountTransfer = amount;
totalSupplyTransfer = totalSupply;
aliceTransfer = alice;
bobTransfer = bob;
}
function test_transfer_tokenTransfers() public {
_mintTransferTokens(aliceTransfer, amountTransfer);
assertEq(tokenTransfer.balanceOf(aliceTransfer), amountTransfer);
assertEq(tokenTransfer.balanceOf(bobTransfer), 0);
vm.prank(aliceTransfer);
bool success = tokenTransfer.transfer(bobTransfer, amountTransfer);
assertEq(success, true);
assertEq(tokenTransfer.balanceOf(bobTransfer), amountTransfer);
assertEq(tokenTransfer.balanceOf(aliceTransfer), 0);
}
function test_transfer_transferFuzzing(uint64 fuzzingTransferAmount) public {
if (totalSupplyTransfer == 0) vm.assume(fuzzingTransferAmount > 0);
else vm.assume(fuzzingTransferAmount > 0 && fuzzingTransferAmount < totalSupplyTransfer);
_mintTransferTokens(aliceTransfer, fuzzingTransferAmount);
assertEq(tokenTransfer.balanceOf(aliceTransfer), fuzzingTransferAmount);
assertEq(tokenTransfer.balanceOf(bobTransfer), 0);
vm.prank(aliceTransfer);
bool success = tokenTransfer.transfer(bobTransfer, fuzzingTransferAmount);
assertEq(success, true);
assertEq(tokenTransfer.balanceOf(bobTransfer), fuzzingTransferAmount);
assertEq(tokenTransfer.balanceOf(aliceTransfer), 0);
}
function test_transfer_couldNotTransferMoreThanAvailable() public {
_mintTransferTokens(aliceTransfer, amountTransfer);
vm.expectRevert();
vm.prank(aliceTransfer);
tokenTransfer.transfer(bobTransfer, type(uint256).max);
}
function test_transfer_doesNotChangeTotalSupply() public {
assertEq(tokenTransfer.totalSupply(), totalSupplyTransfer);
_mintTransferTokens(aliceTransfer, amountTransfer);
if (totalSupplyTransfer == 0) {
assertEq(tokenTransfer.totalSupply(), amountTransfer);
vm.prank(aliceTransfer);
tokenTransfer.transfer(bobTransfer, amountTransfer);
assertEq(tokenTransfer.totalSupply(), amountTransfer);
} else {
assertEq(tokenTransfer.totalSupply(), totalSupplyTransfer);
vm.prank(aliceTransfer);
tokenTransfer.transfer(bobTransfer, amountTransfer);
assertEq(tokenTransfer.totalSupply(), totalSupplyTransfer);
}
}
function _mintTransferTokens(address who, uint256 value) internal virtual;
}