306 lines
8.1 KiB
Solidity
306 lines
8.1 KiB
Solidity
pragma solidity 0.8.20;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import "../../src/mocks/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;
|
|
}
|