From f90def33f40a8ea29e69587f1f87c51dae5a6550 Mon Sep 17 00:00:00 2001 From: Mikhail Melnik Date: Sat, 20 Apr 2024 02:53:52 +0400 Subject: [PATCH] add permit2 witness proxy example --- contracts/extensions/Permit2WitnessProxy.sol | 96 ++++++++++++++++++++ package.json | 1 + test/WitnessProxyExample.js | 88 ++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 contracts/extensions/Permit2WitnessProxy.sol create mode 100644 test/WitnessProxyExample.js diff --git a/contracts/extensions/Permit2WitnessProxy.sol b/contracts/extensions/Permit2WitnessProxy.sol new file mode 100644 index 00000000..78254879 --- /dev/null +++ b/contracts/extensions/Permit2WitnessProxy.sol @@ -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 */ diff --git a/package.json b/package.json index 61fab641..f753460e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/WitnessProxyExample.js b/test/WitnessProxyExample.js new file mode 100644 index 00000000..52e8db61 --- /dev/null +++ b/test/WitnessProxyExample.js @@ -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); + }); +});