Compare commits
2 Commits
7fa376a1a7
...
c3e0d54be4
Author | SHA1 | Date | |
---|---|---|---|
c3e0d54be4 | |||
1f3dd1be96 |
@ -31,9 +31,7 @@ library UniswapV2Library {
|
||||
bytes1(0xff),
|
||||
factory,
|
||||
keccak256(abi.encodePacked(token0, token1)),
|
||||
// hex"443533a897cfad2762695078bf6ee9b78b4edcda64ec31e1c83066cee4c90a7e" // init code hash
|
||||
// hex"7b5d0bf8129b1dafc6a0c5ca9822522c5c0c69ebc66d6af886ae16fa5b9db6ef" // init code hash
|
||||
hex"2512d6f091a7cc78ba777ddd98c57630a8cbbf9e4afca50e51170141f5029973" // init code hash
|
||||
hex"430c098d71e70091b6b419a06af9259b7847ea60cfa63fb719a0c9473f6cbb49" // init code hash
|
||||
)
|
||||
)
|
||||
)
|
||||
|
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);
|
||||
}
|
||||
|
||||
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));
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
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