Skip to content

Commit 096453a

Browse files
committed
feat: support Permit (ERC-2612)
1 parent c69c5a8 commit 096453a

File tree

4 files changed

+60
-7
lines changed

4 files changed

+60
-7
lines changed

.solhint.other.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"gas-indexed-events": "warn",
3838
"gas-multitoken1155": "warn",
3939
"gas-named-return-values": "warn",
40-
"gas-small-strings": "warn",
40+
"gas-small-strings": "off",
4141
"gas-struct-packing": "warn",
4242

4343
"quotes": "error",
@@ -49,13 +49,13 @@
4949
"avoid-tx-origin": "error",
5050
"check-send-result": "error",
5151
"compiler-version": ["error", "^0.8.24"],
52-
"func-visibility": ["error", { "ignoreConstructors": true }],
52+
"func-visibility": ["error", {"ignoreConstructors": true}],
5353

5454
"multiple-sends": "error",
5555
"no-complex-fallback": "error",
5656
"no-inline-assembly": "error",
5757
"not-rely-on-block-hash": "error",
58-
"not-rely-on-time": "error",
58+
"not-rely-on-time": "off",
5959
"reentrancy": "error",
6060
"state-visibility": "error"
6161
}

src/XanV1.sol

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@ pragma solidity ^0.8.30;
33

44
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
55
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6-
76
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
87
import {ERC20BurnableUpgradeable} from
98
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
9+
import {ERC20PermitUpgradeable} from
10+
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
11+
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
1012
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
13+
1114
import {Time} from "@openzeppelin/contracts/utils/types/Time.sol";
1215

1316
import {IXanV1} from "./interfaces/IXanV1.sol";
1417
import {Parameters} from "./libs/Parameters.sol";
1518
import {Ranking} from "./libs/Ranking.sol";
1619

17-
contract XanV1 is IXanV1, Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, UUPSUpgradeable {
20+
contract XanV1 is
21+
IXanV1,
22+
Initializable,
23+
ERC20Upgradeable,
24+
ERC20PermitUpgradeable,
25+
ERC20BurnableUpgradeable,
26+
UUPSUpgradeable
27+
{
1828
using Ranking for Ranking.ProposedUpgrades;
1929

2030
/// @notice The [ERC-7201](https://eips.ethereum.org/EIPS/eip-7201) storage of the contract.
@@ -243,7 +253,11 @@ contract XanV1 is IXanV1, Initializable, ERC20Upgradeable, ERC20BurnableUpgradea
243253
function __XanV1_init(address initialMintRecipient) internal onlyInitializing {
244254
// Initialize inherited contracts
245255
__Context_init_unchained();
256+
246257
__ERC20_init_unchained({name_: Parameters.NAME, symbol_: Parameters.SYMBOL});
258+
__ERC20Permit_init(Parameters.NAME); // TODO! Revisit OZ
259+
//__ERC20Permit_init_unchained(tokenName);
260+
//__EIP712_init_unchained({name: tokenName, version: "1"});
247261
__ERC20Burnable_init_unchained();
248262
__UUPSUpgradeable_init_unchained();
249263

test/XanV1.unit.t.sol

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ contract XanV1UnitTest is Test {
2121
function setUp() public {
2222
(, _defaultSender,) = vm.readCallers();
2323

24-
vm.prank(_defaultSender);
2524
_xanProxy = XanV1(
2625
Upgrades.deployUUPSProxy({
2726
contractName: "XanV1.sol:XanV1",
@@ -626,6 +625,46 @@ contract XanV1UnitTest is Test {
626625
assertEq(_xanProxy.lockedSupply(), 3 * valueToLock);
627626
}
628627

628+
function test_permits_spending_given_an_EIP712_signature() public {
629+
uint256 alicePrivKey = 0xA11CE;
630+
address aliceAddr = vm.addr(alicePrivKey);
631+
address spender = address(uint160(4));
632+
633+
// Give funds to Alice
634+
vm.prank(_defaultSender);
635+
_xanProxy.transfer({to: aliceAddr, value: 1_000});
636+
637+
// Sign message
638+
uint256 nonce = _xanProxy.nonces(aliceAddr);
639+
uint256 deadline = block.timestamp + 1 hours;
640+
uint256 value = 500;
641+
642+
bytes32 structHash = keccak256(
643+
abi.encode(
644+
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
645+
aliceAddr,
646+
spender,
647+
value,
648+
nonce,
649+
deadline
650+
)
651+
);
652+
653+
bytes32 domainSeparator = _xanProxy.DOMAIN_SEPARATOR();
654+
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
655+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePrivKey, digest);
656+
657+
// Check that the spender is allowed to spend 0 XAN of Alice before the `permit` call.
658+
assertEq(_xanProxy.allowance({owner: aliceAddr, spender: spender}), 0);
659+
660+
// Given the signature, anyone (here `_defaultSender`) can set the allowance.
661+
vm.prank(_defaultSender);
662+
_xanProxy.permit({owner: aliceAddr, spender: spender, value: value, deadline: deadline, v: v, r: r, s: s});
663+
664+
// Check that the spender is allowed to spend `value` XAN of Alice after the `permit` call.
665+
assertEq(_xanProxy.allowance({owner: aliceAddr, spender: spender}), value);
666+
}
667+
629668
function test_initialize_mints_the_expected_supply_amounting_to_1_billion_tokens() public view {
630669
uint256 expectedTokens = 10 ** 9;
631670

0 commit comments

Comments
 (0)