Skip to content
Merged
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
13 changes: 10 additions & 3 deletions .github/workflows/npm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ on:
- latest
- beta

permissions:
id-token: write # Required for OIDC
contents: read

jobs:
npm-upload:
name: NPM Package Build and Upload
Expand All @@ -29,10 +33,15 @@ jobs:
- name: Checkout
uses: actions/checkout@v2

- uses: actions/setup-node@v3
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
registry-url: 'https://registry.npmjs.org'

# Ensure npm 11.5.1 or later is installed
- name: Update npm
run: npm install -g npm@latest

- run: npm ci

Expand All @@ -47,8 +56,6 @@ jobs:

- run: scripts/make-npm-package.sh "${{ steps.semver.outputs.semver }}" ./build/npm-package

- run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTOMATION_TOKEN }}" > ./build/npm-package/.npmrc

- run: |
RELEASE_TAG="${{ github.event.inputs.tag }}"
if [ -z "$RELEASE_TAG" ]; then
Expand Down
31 changes: 31 additions & 0 deletions contracts/TestCurrencyPermit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";

contract TestCurrencyPermit is ERC20Permit {
uint8 internal immutable _decimals;

constructor(
string memory name_,
string memory symbol_,
uint256 initialSupply,
uint8 decimals_
) ERC20(name_, symbol_) ERC20Permit(name_) {
_decimals = decimals_;
_mint(msg.sender, initialSupply);
}

function decimals() public view virtual override returns (uint8) {
return _decimals;
}

function mint(address recipient, uint256 amount) public virtual {
return _mint(recipient, amount);
}

function burn(address recipient, uint256 amount) public virtual {
return _burn(recipient, amount);
}
}
67 changes: 66 additions & 1 deletion js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
const { findAll } = require("solidity-ast/utils");
const ethers = require("ethers");
const withArgsInternal = require("@nomicfoundation/hardhat-chai-matchers/internal/withArgs");
const { IMPLEMENTATION_SLOT } = require("./constants");
const helpers = require("@nomicfoundation/hardhat-network-helpers");
const { IMPLEMENTATION_SLOT, HOUR } = require("./constants");

const _E = ethers.parseEther;
const WAD = 10n ** 18n; // 1e18
Expand Down Expand Up @@ -351,6 +352,69 @@ function newCaptureAny() {

const captureAny = newCaptureAny();

async function makeEIP2612Signature(hre, token, owner, spenderAddress, value, deadline = HOUR) {
// From: https://www.quicknode.com/guides/ethereum-development/transactions/how-to-use-erc20-permit-approval
const chainId = hre.network.config.chainId;
// set the domain parameters
const tokenAddr = await ethers.resolveAddress(token);
const domain = {
name: await token.name(),
version: "1",
chainId: chainId,
verifyingContract: tokenAddr,
};

// set the Permit type parameters
const types = {
Permit: [
{
name: "owner",
type: "address",
},
{
name: "spender",
type: "address",
},
{
name: "value",
type: "uint256",
},
{
name: "nonce",
type: "uint256",
},
{
name: "deadline",
type: "uint256",
},
],
};

if (deadline < 1600000000) {
// Is a duration in seconds
deadline = (await helpers.time.latest()) + deadline;
}

const nonces = await token.nonces(owner);

// set the Permit type values
const ownerAddr = await ethers.resolveAddress(owner);
const values = {
owner: ownerAddr,
spender: spenderAddress,
value: value,
nonce: nonces,
deadline: deadline,
};

// sign the Permit type data with the deployer's private key
const signature = await owner.signTypedData(domain, types, values);

// split the signature into its components
const sig = ethers.Signature.from(signature);
return { sig, deadline, nonces };
}

module.exports = {
_E,
_A,
Expand Down Expand Up @@ -381,4 +445,5 @@ module.exports = {
captureAny,
newCaptureAny,
AM_ROLES,
makeEIP2612Signature,
};
44 changes: 43 additions & 1 deletion test/test-utils-functions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const hre = require("hardhat");
const { expect } = require("chai");
const helpers = require("@nomicfoundation/hardhat-network-helpers");
const { _A, getRole, grantRole } = require("../js/utils");
const { _A, getRole, grantRole, makeEIP2612Signature } = require("../js/utils");
const { initCurrency } = require("../js/test-utils");

const { ethers } = hre;
Expand Down Expand Up @@ -36,6 +36,23 @@ describe("Utils library tests", function () {
return { currency };
}

async function deployFixturePermit() {
// Fixture with TestCurrency (without access control)
const currency = await initCurrency(
{
name: "Test USDC",
symbol: "USDC",
decimals: 6,
initial_supply: _A(50000),
contractClass: "TestCurrencyPermit",
},
[anon, user1, user2, admin],
[_A("10000"), _A("2000"), _A("1000"), _A("20000")]
);

return { currency };
}

it("Checks only MINTER_ROLE can mint (TestCurrencyAC)", async () => {
const { currency } = await helpers.loadFixture(deployACFixture);

Expand Down Expand Up @@ -63,6 +80,31 @@ describe("Utils library tests", function () {
expect(await currency.balanceOf(anon)).to.equal(_A(9950));
});

it("Checks gasless spending approvals (TestCurrencyPermit)", async () => {
const { currency } = await helpers.loadFixture(deployFixturePermit);

expect(await currency.balanceOf(user1)).to.equal(_A(2000));
expect(await currency.balanceOf(user2)).to.equal(_A(1000));

const { sig, deadline } = await makeEIP2612Signature(
hre,
currency,
user1,
await ethers.resolveAddress(user2),
_A(200)
);
await expect(currency.permit(user1, user2, _A(200), deadline, sig.v, sig.r, sig.s))
.to.emit(currency, "Approval")
.withArgs(user1, user2, _A(200));

await expect(currency.connect(user2).transferFrom(user1, user2, _A(60)))
.to.emit(currency, "Transfer")
.withArgs(user1, user2, _A(60));

expect(await currency.balanceOf(user1)).to.equal(_A(2000 - 60));
expect(await currency.balanceOf(user2)).to.equal(_A(1000 + 60));
});

it("Checks TestERC4626", async () => {
const { currency } = await helpers.loadFixture(deployACFixture);

Expand Down
Loading