Skip to content

Commit

Permalink
updated example to have permit signature verification
Browse files Browse the repository at this point in the history
  • Loading branch information
leekt committed Jul 30, 2024
1 parent 848a911 commit c8c2625
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 41 deletions.
66 changes: 66 additions & 0 deletions src/PermitEnforcer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
pragma solidity ^0.8.23;

import {ISignatureEnforcer} from "./interfaces/ISignatureEnforcer.sol";
import {MessageHashUtils} from "src/DelegationManagerBatch.sol";

struct PermitTerms {
address owner;
address spender;
uint256 maximum;
}

struct PermitArgs {
uint256 value;
uint256 nonce;
uint256 deadline;
}

interface IUSDC {
function balanceOf(address account) external view returns (uint256);
function mint(address to, uint256 amount) external;
function configureMinter(address minter, uint256 minterAllowedAmount) external;
function masterMinter() external view returns (address);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) external;
function nonces(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
}
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")

bytes32 constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

contract PermitEnforcer is ISignatureEnforcer {
IUSDC immutable usdc;

constructor(IUSDC _usdc) {
usdc = _usdc;
}

function checkSignatureVerification( // temporary name
bytes calldata _terms,
bytes calldata _args,
address _requestor, // address of the contract that called initial DELEGATOR.isValidSignature(bytes32 hash, bytes calldata signature)
bytes32 _messageHash, // hash that was given on initial DELEGATOR.isValidSignature(bytes32 hash, bytes calldata signature)
bytes32 _delegateionHash,
address _delegator,
address _redeemer
) external view returns (bool) {
PermitTerms memory pt = parseTerms(_terms);
PermitArgs memory pa = parseArgs(_args);
require(pt.maximum >= pa.value, "value exceeds maximum");
bytes32 generatedTypedDataHash = MessageHashUtils.toTypedDataHash(
usdc.DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, pt.owner, pt.spender, pa.value, pa.nonce, pa.deadline))
);
require(generatedTypedDataHash == _messageHash, "message hash does not match args/terms");
return _requestor == address(usdc);
}

function parseTerms(bytes calldata _terms) internal pure returns (PermitTerms memory) {
return abi.decode(_terms, (PermitTerms));
}

function parseArgs(bytes calldata _args) internal pure returns (PermitArgs memory) {
return abi.decode(_args, (PermitArgs));
}
}
18 changes: 0 additions & 18 deletions src/SignatureRequestorEnforcer.sol

This file was deleted.

94 changes: 71 additions & 23 deletions test/DMYi.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
import "forge-std/console.sol";
import {ECDSA} from "solady/utils/ECDSA.sol";
import {ExecMode, ExecLib} from "kernel/src/utils/ExecLib.sol";
import {SignatureRequestorEnforcer} from "src/SignatureRequestorEnforcer.sol";
import {IUSDC, PERMIT_TYPEHASH, PermitEnforcer, PermitTerms, PermitArgs} from "src/PermitEnforcer.sol";

contract MockCallee {
mapping(address caller => uint256) public barz;
Expand All @@ -39,20 +39,6 @@ contract MockERC20 is ERC20 {
}
}

// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
bytes32 constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

interface IUSDC {
function balanceOf(address account) external view returns (uint256);
function mint(address to, uint256 amount) external;
function configureMinter(address minter, uint256 minterAllowedAmount) external;
function masterMinter() external view returns (address);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) external;
function nonces(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
}

contract DMTest is Test {
DelegationManagerBatch public dm;
SubAccountFactory public factory;
Expand Down Expand Up @@ -97,9 +83,9 @@ contract DMTest is Test {
}

function testSessionKeyUseOnlyUSDCPermit() external {
SignatureRequestorEnforcer signatureRequestorEnforcer = new SignatureRequestorEnforcer();
//// USDC contract address on mainnet
IUSDC usdc = IUSDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
PermitEnforcer signatureRequestorEnforcer = new PermitEnforcer(usdc);

// spoof .configureMinter() call with the master minter account
vm.prank(usdc.masterMinter());
Expand All @@ -109,6 +95,8 @@ contract DMTest is Test {
usdc.mint(address(this), 1000e6);
vm.stopPrank();

address spender = makeAddr("Spender");

Delegation[] memory d = new Delegation[](2);
Caveat[] memory e = new Caveat[](0);
d[1] = Delegation({
Expand All @@ -121,8 +109,17 @@ contract DMTest is Test {
});
console.log("Master : ", master);
// delegate usdc permit signature to session
uint256 allowance = 1000;
Caveat[] memory c = new Caveat[](1);
c[0] = Caveat({enforcer: address(signatureRequestorEnforcer), terms: abi.encode(address(usdc)), args: hex""});
PermitTerms memory pt = PermitTerms({owner: address(subAccount), spender: spender, maximum: 10000});
PermitArgs memory pa = PermitArgs({value: 1000, nonce: usdc.nonces(owner), deadline: block.timestamp + 1000});

bytes32 permitHash = MessageHashUtils.toTypedDataHash(
usdc.DOMAIN_SEPARATOR(),
keccak256(abi.encode(PERMIT_TYPEHASH, pt.owner, pt.spender, pa.value, pa.nonce, pa.deadline))
);

c[0] = Caveat({enforcer: address(signatureRequestorEnforcer), terms: abi.encode(pt), args: abi.encode(pa)});
d[0] = Delegation({
delegate: session,
delegator: master,
Expand All @@ -133,18 +130,69 @@ contract DMTest is Test {
});
d[0].signature = signDelegation(d[0], masterKey);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionKey, permitHash);
usdc.permit(
address(subAccount),
spender,
allowance,
block.timestamp + 1000,
abi.encode(d, bytes.concat(r, s, bytes1(v)))
);

assertEq(usdc.allowance(address(subAccount), spender), allowance);
}

function testSessionKeyUseOnlyUSDCPermitExceedsMaximum() external {
//// USDC contract address on mainnet
IUSDC usdc = IUSDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);
PermitEnforcer signatureRequestorEnforcer = new PermitEnforcer(usdc);

// spoof .configureMinter() call with the master minter account
vm.prank(usdc.masterMinter());
// allow this test contract to mint USDC
usdc.configureMinter(address(this), type(uint256).max);
// mint $1000 USDC to the test contract (or an external user)
usdc.mint(address(this), 1000e6);
vm.stopPrank();

address spender = makeAddr("Spender");

Delegation[] memory d = new Delegation[](2);
Caveat[] memory e = new Caveat[](0);
d[1] = Delegation({
delegate: master,
delegator: address(subAccount),
authority: ROOT_AUTHORITY,
caveats: e,
salt: 0,
signature: hex""
});
console.log("Master : ", master);
// delegate usdc permit signature to session
uint256 allowance = 1000;
Caveat[] memory c = new Caveat[](1);
PermitTerms memory pt = PermitTerms({owner: address(subAccount), spender: spender, maximum: 10000});
PermitArgs memory pa =
PermitArgs({value: pt.maximum + 1, nonce: usdc.nonces(owner), deadline: block.timestamp + 1000});

bytes32 permitHash = MessageHashUtils.toTypedDataHash(
usdc.DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
PERMIT_TYPEHASH, address(subAccount), spender, allowance, usdc.nonces(owner), block.timestamp + 1000
)
)
keccak256(abi.encode(PERMIT_TYPEHASH, pt.owner, pt.spender, pa.value, pa.nonce, pa.deadline))
);

c[0] = Caveat({enforcer: address(signatureRequestorEnforcer), terms: abi.encode(pt), args: abi.encode(pa)});
d[0] = Delegation({
delegate: session,
delegator: master,
authority: dm.getDelegationHash(d[1]),
caveats: c,
salt: 0,
signature: hex""
});
d[0].signature = signDelegation(d[0], masterKey);

(uint8 v, bytes32 r, bytes32 s) = vm.sign(sessionKey, permitHash);
vm.expectRevert();
usdc.permit(
address(subAccount),
spender,
Expand All @@ -153,7 +201,7 @@ contract DMTest is Test {
abi.encode(d, bytes.concat(r, s, bytes1(v)))
);

assertEq(usdc.allowance(address(subAccount), spender), allowance);
assertEq(usdc.allowance(address(subAccount), spender), 0);
}

function signDelegation(Delegation memory delegation, uint256 key) internal returns (bytes memory) {
Expand Down

0 comments on commit c8c2625

Please sign in to comment.