Skip to content
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
33 changes: 33 additions & 0 deletions test/SignatureTransfer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {SignatureTransfer} from "../src/SignatureTransfer.sol";
import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol";
import {ISignatureTransfer} from "../src/interfaces/ISignatureTransfer.sol";
import {InvalidNonce, SignatureExpired} from "../src/PermitErrors.sol";
import {IPermit2} from "../src/interfaces/IPermit2.sol";
import {MockContract} from "./mocks/MockContract.sol";

contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnapshot {
using AddressBuilder for address[];
Expand Down Expand Up @@ -58,12 +60,14 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps

address address0 = address(0x0);
address address2 = address(0x2);
MockContract mockContract;

bytes32 DOMAIN_SEPARATOR;

function setUp() public {
permit2 = new Permit2();
DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR();
mockContract = new MockContract(IPermit2(address(permit2)));

fromPrivateKey = 0x12341234;
from = vm.addr(fromPrivateKey);
Expand Down Expand Up @@ -109,6 +113,35 @@ contract SignatureTransferTest is Test, PermitSignature, TokenProvider, GasSnaps
assertEq(token0.balanceOf(address2), startBalanceTo + defaultAmount);
}

function testPermitTransferFromContract() public {
uint256 nonce = 0;
ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce);
bytes memory sig = getPermitTransferSignature(permit, address(mockContract), fromPrivateKey, DOMAIN_SEPARATOR);

uint256 startBalance = token0.balanceOf(from);

uint256 amount = 1e18; // default amount in defaultERC20PermitTransfer
vm.prank(from);
mockContract.depositWithPermit(address(token0), amount, permit, sig);

// token0 transferred from `from` to `mockContract`
assertEq(token0.balanceOf(address(mockContract)), amount);
assertEq(token0.balanceOf(address(from)), startBalance - amount);
}

function test_revert_PermitTransferFromContract() public {
uint256 nonce = 0;
ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce);

// the default signature, where the signer approves to=address(this)
// signature will fail when a contract (invalid recipient) tries to use it
bytes memory sig = getPermitTransferSignature(permit, fromPrivateKey, DOMAIN_SEPARATOR);

uint256 amount = 1e18;
vm.expectRevert();
mockContract.depositWithPermit(address(token0), amount, permit, sig);
}

function testPermitTransferFromCompactSig() public {
uint256 nonce = 0;
ISignatureTransfer.PermitTransferFrom memory permit = defaultERC20PermitTransfer(address(token0), nonce);
Expand Down
24 changes: 24 additions & 0 deletions test/mocks/MockContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "../../src/interfaces/IPermit2.sol";

contract MockContract {
IPermit2 public immutable permit2;

constructor(IPermit2 _permit2) {
permit2 = _permit2;
}

function depositWithPermit(
address token,
uint256 amount,
ISignatureTransfer.PermitTransferFrom calldata permitData,
bytes calldata sig
) external {
require(token == permitData.permitted.token, "MockContract: token mismatch");
permit2.permitTransferFrom(
permitData, ISignatureTransfer.SignatureTransferDetails(address(this), amount), msg.sender, sig
);
}
}
24 changes: 21 additions & 3 deletions test/utils/PermitSignature.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,20 +131,38 @@ contract PermitSignature {
return bytes.concat(r, s, bytes1(v));
}

/// @notice Get the permitted signature for a transferFrom to the current contract (address(this))
/// @param permit The permit to sign
/// @param privateKey The private key to sign with, provided to vm.sign
/// @param domainSeparator The domain separator for the permit
/// @return sig The concatenated signature, authorizing a transfer from signer to the current contract
function getPermitTransferSignature(
ISignatureTransfer.PermitTransferFrom memory permit,
uint256 privateKey,
bytes32 domainSeparator
) internal view returns (bytes memory sig) {
return getPermitTransferSignature(permit, address(this), privateKey, domainSeparator);
}

/// @notice Get the permitted signature for a transferFrom to a designated address
/// @param permit The permit to sign
/// @param to The recipient of the transferFrom
/// @param privateKey The private key to sign with, provided to vm.sign
/// @param domainSeparator The domain separator for the permit
/// @return sig The concatenated signature, authorizing a transfer from signer to the provided address
function getPermitTransferSignature(
ISignatureTransfer.PermitTransferFrom memory permit,
address to,
uint256 privateKey,
bytes32 domainSeparator
) internal pure returns (bytes memory sig) {
bytes32 tokenPermissions = keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, permit.permitted));
bytes32 msgHash = keccak256(
abi.encodePacked(
"\x19\x01",
domainSeparator,
keccak256(
abi.encode(
_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissions, address(this), permit.nonce, permit.deadline
)
abi.encode(_PERMIT_TRANSFER_FROM_TYPEHASH, tokenPermissions, to, permit.nonce, permit.deadline)
)
)
);
Expand Down