additional tests added and extra mocks added
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
This commit is contained in:
parent
1f3dd1be96
commit
c3e0d54be4
57
src/mock/SigUtils.sol
Normal file
57
src/mock/SigUtils.sol
Normal 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
79
test/UniswapV2ERC20.t.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,19 @@ contract UniswapV2FactoryTest is Test {
|
|||||||
assertEq(UniswapV2Pair(tokenPair).token1(), _token1);
|
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 {
|
function testCreatePairMultipleTokens() public {
|
||||||
address tokenPair0 = factory.createPair(address(token0), address(token1));
|
address tokenPair0 = factory.createPair(address(token0), address(token1));
|
||||||
address tokenPair1 = factory.createPair(address(token2), address(token3));
|
address tokenPair1 = factory.createPair(address(token2), address(token3));
|
||||||
|
@ -45,7 +45,6 @@ contract TestUniswapV2Pair is Test {
|
|||||||
factory = new UniswapV2Factory(address(0));
|
factory = new UniswapV2Factory(address(0));
|
||||||
address tokenAddress = factory.createPair(address(token0), address(token1));
|
address tokenAddress = factory.createPair(address(token0), address(token1));
|
||||||
pair = UniswapV2Pair(tokenAddress);
|
pair = UniswapV2Pair(tokenAddress);
|
||||||
// pair.initialize(address(token0), address(token1));
|
|
||||||
|
|
||||||
token0.mint(address(this), 10 ether);
|
token0.mint(address(this), 10 ether);
|
||||||
token1.mint(address(this), 10 ether);
|
token1.mint(address(this), 10 ether);
|
||||||
|
@ -22,6 +22,9 @@ contract TestUniswapV2Router is Test {
|
|||||||
|
|
||||||
WETH9 public weth;
|
WETH9 public weth;
|
||||||
|
|
||||||
|
fallback() external payable {}
|
||||||
|
receive() external payable {}
|
||||||
|
|
||||||
function setUp() public {
|
function setUp() public {
|
||||||
weth = new WETH9();
|
weth = new WETH9();
|
||||||
factory = new UniswapV2Factory(address(0));
|
factory = new UniswapV2Factory(address(0));
|
||||||
@ -34,6 +37,34 @@ contract TestUniswapV2Router is Test {
|
|||||||
token1.mint(address(this), 10 ether);
|
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 {
|
function testAddLiquidityPairFor() public {
|
||||||
token0.approve(address(router), 10 ether);
|
token0.approve(address(router), 10 ether);
|
||||||
token1.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 {
|
function testRemoveLiquidity() public {
|
||||||
token0.approve(address(router), 1 ether);
|
token0.approve(address(router), 1 ether);
|
||||||
token1.approve(address(router), 1 ether);
|
token1.approve(address(router), 1 ether);
|
||||||
|
80
test/tokens/Allowance.t.sol
Normal file
80
test/tokens/Allowance.t.sol
Normal 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
306
test/tokens/Permit.t.sol
Normal 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;
|
||||||
|
}
|
78
test/tokens/Transfer.t.sol
Normal file
78
test/tokens/Transfer.t.sol
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user