-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
185 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |