Skip to content

Commit

Permalink
add permit2 witness proxy example
Browse files Browse the repository at this point in the history
  • Loading branch information
ZumZoom committed Apr 19, 2024
1 parent 5455f34 commit f90def3
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 0 deletions.
96 changes: 96 additions & 0 deletions contracts/extensions/Permit2WitnessProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.23;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "./ImmutableOwner.sol";

interface Permit2 {
struct TokenPermissions {
// ERC20 token address
address token;
// the maximum amount that can be spent
uint256 amount;
}

struct PermitTransferFrom {
TokenPermissions permitted;
// a unique value for every token owner's signature to prevent signature replays
uint256 nonce;
// deadline on the permit signature
uint256 deadline;
}

struct SignatureTransferDetails {
// recipient address
address to;
// spender requested amount
uint256 requestedAmount;
}

function permitWitnessTransferFrom(
PermitTransferFrom calldata permit,
SignatureTransferDetails calldata transferDetails,
address owner,
bytes32 witness,
string calldata witnessTypeString,
bytes calldata signature
) external;
}

/* solhint-disable func-name-mixedcase */

contract Permit2WitnessProxy is ImmutableOwner {
error Permit2WitnessProxyBadSelector();

struct Witness {
bytes32 salt;
}

string private constant _WITNESS_TYPE_STRING =
"Witness witness)TokenPermissions(address token,uint256 amount)Witness(bytes32 salt)";

bytes32 private constant _WITNESS_TYPEHASH = keccak256("Witness(bytes32 salt)");

Permit2 private constant _PERMIT2 = Permit2(0x000000000022D473030F116dDEE9F6B43aC78BA3);

constructor(address _immutableOwner) ImmutableOwner(_immutableOwner) {
if (Permit2WitnessProxy.func_20glDB1.selector != IERC20.transferFrom.selector) revert Permit2WitnessProxyBadSelector();
}

/// @notice Proxy transfer method for `Permit2.permitWitnessTransferFrom`. Selector must match `IERC20.transferFrom`
// keccak256("func_20glDB1(address,address,uint256,address,uint256,uint256,uint256,bytes32,bytes)") == 0x23b872dd (IERC20.transferFrom)
function func_20glDB1(
address from,
address to,
uint256 amount,
address token,
uint256 permittedAmount,
uint256 nonce,
uint256 deadline,
bytes32 salt,
bytes calldata sig
) external onlyImmutableOwner {
_PERMIT2.permitWitnessTransferFrom(
Permit2.PermitTransferFrom({
permitted: Permit2.TokenPermissions({
token: token,
amount: permittedAmount
}),
nonce: nonce,
deadline: deadline
}),
Permit2.SignatureTransferDetails({
to: to,
requestedAmount: amount
}),
from,
keccak256(abi.encode(_WITNESS_TYPEHASH, Witness(salt))),
_WITNESS_TYPE_STRING,
sig
);
}
}

/* solhint-enable func-name-mixedcase */
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@nomicfoundation/hardhat-network-helpers": "1.0.10",
"@nomicfoundation/hardhat-verify": "2.0.3",
"@nomicfoundation/hardhat-ethers": "3.0.5",
"@uniswap/permit2-sdk": "1.2.0",
"chai": "4.4.0",
"dotenv": "16.3.1",
"eslint": "8.56.0",
Expand Down
88 changes: 88 additions & 0 deletions test/WitnessProxyExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const { constants, permit2Contract } = require('@1inch/solidity-utils');
const { SignatureTransfer, PERMIT2_ADDRESS } = require('@uniswap/permit2-sdk');
const { ether } = require('./helpers/utils');
const { signOrder, buildOrder, buildTakerTraits } = require('./helpers/orderUtils');
const { deploySwapTokens } = require('./helpers/fixtures');
const hre = require('hardhat');
const { ethers } = hre;

describe('WitnessProxyExample', function () {
let addr, addr1;

before(async function () {
[addr, addr1] = await ethers.getSigners();
});

it('permit2 witness example', async function () {
const { dai, weth, swap, chainId } = await deploySwapTokens();

await dai.mint(addr, ether('2000'));
await weth.connect(addr1).deposit({ value: ether('1') });

await dai.approve(swap, ether('2000'));
await weth.connect(addr1).approve(PERMIT2_ADDRESS, ether('1'));

const Permit2WitnessProxy = await ethers.getContractFactory('Permit2WitnessProxy');
const permit2WitnessProxy = await Permit2WitnessProxy.deploy(await swap.getAddress());
await permit2WitnessProxy.waitForDeployment();

await permit2Contract();

const permit = {
permitted: {
token: await weth.getAddress(),
amount: ether('1'),
},
spender: await permit2WitnessProxy.getAddress(),
nonce: 0,
deadline: 0xffffffff,
};

const witness = {
witness: { salt: '0x0000000000000000000000000000000000000000000000000000000000000000' },
witnessTypeName: 'Witness',
witnessType: { Witness: [{ name: 'salt', type: 'bytes32' }] },
};

const data = SignatureTransfer.getPermitData(
permit,
PERMIT2_ADDRESS,
31337,
witness,
);

const sig = ethers.Signature.from(await addr1.signTypedData(data.domain, data.types, data.values));

const makerAssetSuffix = '0x' + permit2WitnessProxy.interface.encodeFunctionData('func_20glDB1', [
constants.ZERO_ADDRESS, constants.ZERO_ADDRESS, 0,
permit.permitted.token,
permit.permitted.amount,
permit.nonce,
permit.deadline,
witness.witness.salt,
sig.compactSerialized,
]).substring(202);

const order = buildOrder(
{
makerAsset: await permit2WitnessProxy.getAddress(),
takerAsset: await dai.getAddress(),
makingAmount: ether('1'),
takingAmount: ether('2000'),
maker: addr1.address,
},
{
makerAssetSuffix,
},
);

const { r, yParityAndS: vs } = ethers.Signature.from(await signOrder(order, chainId, await swap.getAddress(), addr1));
const takerTraits = buildTakerTraits({
makingAmount: true,
extension: order.extension,
threshold: order.takingAmount,
});

await swap.fillOrderArgs(order, r, vs, order.makingAmount, takerTraits.traits, takerTraits.args);
});
});

0 comments on commit f90def3

Please sign in to comment.