Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add permit method #11

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gemforge.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ module.exports = {
// diamond configuration
diamond: {
// Whether to include public methods when generating the IDiamondProxy interface. Default is to only include external methods.
publicMethods: false,
publicMethods: true,
init: {
contract: "InitDiamond",
function: "init",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@commitlint/config-conventional": "^18.6.2",
"chalk": "4",
"dotenv": "^16.4.5",
"gemforge": "^2.12.1",
"gemforge": "^2.13.1",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"viem": "^2.10.5",
Expand Down
5 changes: 1 addition & 4 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
forge-std/=lib/forge-std/src/
openzeppelin/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/=lib/openzeppelin-contracts/
diamond-2-hardhat/=lib/diamond-2-hardhat/contracts/
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
openzeppelin-contracts/=lib/openzeppelin-contracts/
solady/=lib/solady/src/
65 changes: 65 additions & 0 deletions src/facets/NaymsTokenFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AppStorage, LibAppStorage } from "../shared/AppStorage.sol";
import { Modifiers } from "../shared/Modifiers.sol";
import { LibHelpers } from "../libs/LibHelpers.sol";
import { LibERC20Token } from "../libs/LibERC20Token.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

/**
* @title Nayms token facet.
Expand Down Expand Up @@ -88,4 +89,68 @@ contract NaymsTokenFacet is Modifiers {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.minter;
}

/// @dev The EIP-712 typehash for the permit struct used by the contract
bytes32 public constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

/**
* @dev See {IERC20Permit-permit}.
*/
function permit(
address _owner,
address _spender,
uint256 _value,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
)
external
{
AppStorage storage s = LibAppStorage.diamondStorage();

if (block.timestamp > _deadline) {
revert("ERC20Permit: expired deadline");
}

bytes32 structHash =
keccak256(abi.encode(PERMIT_TYPEHASH, _owner, _spender, _value, s.nonces[_owner]++, _deadline));

bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), structHash));

address signer = ECDSA.recover(digest, _v, _r, _s);
if (signer == address(0) || signer != _owner) {
revert("ERC20Permit: invalid signature");
}

LibERC20Token._approve(_owner, _spender, _value, true);
}

/**
* @dev Returns the current nonce for `owner`. This value must be included whenever a signature is generated for
* {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256) {
AppStorage storage s = LibAppStorage.diamondStorage();
return s.nonces[owner];
}

/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("Naym")), // name
keccak256(bytes("1")), // version
block.chainid,
address(this)
)
);
}
}
3 changes: 2 additions & 1 deletion src/shared/AppStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct AppStorage {
bool diamondInitialized;
//// EIP712 domain separator ////
uint256 initialChainId;
bytes32 initialDomainSeparator;
bytes32 initialDomainSeparator; // note: Deprecated. Use the method DOMAIN_SEPARATOR() in NaymsTokenFacet
//// Reentrancy guard ////
uint256 reentrancyStatus;
//// NAYMS ERC20 TOKEN ////
Expand All @@ -30,6 +30,7 @@ struct AppStorage {
address minter;
// upgrade initializations
mapping(uint256 => bool) initComplete;
mapping(address permitCaller => uint256 nonce) nonces;
}

library LibAppStorage {
Expand Down
58 changes: 58 additions & 0 deletions test/NaymToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,64 @@ contract NaymTokenTest is Test {
assertEq(t.allowance(SENDER, SPENDER), 50);
}

function test_Permit() public {
uint256 privateKey = 0xBEEF;
address owner = vm.addr(privateKey);
address spender = address(0xCAFE);
uint256 value = 1e18;
uint256 deadline = block.timestamp;

// Get the domain separator and compute the permit hash
bytes32 structHash = keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
value,
0, // nonce
deadline
)
);

bytes32 hash = keccak256(abi.encodePacked("\x19\x01", t.DOMAIN_SEPARATOR(), structHash));

(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash);

// Execute permit
t.permit(owner, spender, value, deadline, v, r, s);

// Verify allowance was set
assertEq(t.allowance(owner, spender), value);
assertEq(t.nonces(owner), 1);
}

function test_PermitExpired() public {
uint256 privateKey = 0xBEEF;
address owner = vm.addr(privateKey);
address spender = address(0xCAFE);
uint256 value = 1e18;
uint256 deadline = block.timestamp - 1;

bytes32 structHash = keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
value,
0,
deadline
)
);

bytes32 hash = keccak256(abi.encodePacked("\x19\x01", t.DOMAIN_SEPARATOR(), structHash));

(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash);

// Should revert with expired deadline
vm.expectRevert("ERC20Permit: expired deadline");
t.permit(owner, spender, value, deadline, v, r, s);
}

function scheduleAndUpgradeDiamond(
IDiamondCut.FacetCut[] memory _cut,
address _init,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -647,10 +647,10 @@ function-bind@^1.1.2:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==

gemforge@^2.12.1:
version "2.12.1"
resolved "https://registry.yarnpkg.com/gemforge/-/gemforge-2.12.1.tgz#7b02725359b61e74ba93550c22bfcbb4909f8089"
integrity sha512-3grKf0hHGKUG+TmVW1MOttxRM7yX1XgAZlSgHQOtmK0+V/Nrk4QVLydMNSJeTyFYKYHl10i1qpe7mBPJrWgMmQ==
gemforge@^2.13.1:
version "2.13.1"
resolved "https://registry.yarnpkg.com/gemforge/-/gemforge-2.13.1.tgz#28f32423586fe41b9fe69b324a928ca15a036253"
integrity sha512-cJZbhvD1Zy5RRN2fMVnHdl2TzJLldD1+wNQnJiLeqDjspnaRTBxuVd1jQAZjAL3rQw0XK+LvxkZvvF3r0TEGDQ==
dependencies:
"@solidity-parser/parser" "^0.16.1"
bigval "^1.7.0"
Expand Down
Loading