Implementation ERC20 Permit (EIP 2612) compatible with EIP 1271

Implementation ERC20 Permit (EIP 2612) compatible with EIP 1271
The best way to implement the new Utils Library of SignatureChecker and the integration with ERC20 Permit, and guarantee compatibility with Smart Contract Wallets

https://docs.openzeppelin.com/contracts/4.x/api/utils#SignatureChecker

:computer: Environment
Ethers.js / Hardhart / Node.js / Open Zeppelin Library / ERC20 / Gasless Feature

:memo:Details
Proposal a improve and integration of library of open zeppelin for integrating ERC20 Permit EIP 2612 and EIP 1271 for full compatibility with Smart Contract Wallets like Argent, Gnosis Wallets

:1234: Code to reproduce

    function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
        // solhint-disable-next-line not-rely-on-time
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(
            abi.encode(
                _PERMIT_TYPEHASH,
                owner,
                spender,
                value,
                _useNonce(owner),
                deadline
            )
        );

        bytes32 hash = _hashTypedDataV4(structHash);
		bytes memory signature = rsvToSig(r,s,v);

        // address signer = ECDSAUpgradeable.recover(hash, v, r, s);
		if (AddressUpgradeable.isContract(owner)) {
            try IERC1271Upgradeable(owner).isValidSignature(hash, signature) returns (bytes4 magicValue) {
                require(magicValue == IERC1271Upgradeable(owner).isValidSignature.selector, "ERC20Permit: invalid signature");
            } catch {
                revert("ERC20Permit: invalid signature");
            }
        } else {
            require(ECDSAUpgradeable.recover(hash, v, r, s) == owner , "ERC20Permit: invalid signature");
        }

        _approve(owner, spender, value);
    }

and function for get signature of r,v,s values (ls any best way to do that appreciate the comments)

    function rsvToSig(bytes32 _a, bytes32 _b, uint8 _c) public returns (bytes memory){
        // string memory str = string(_bytes32);
        // TypeError: Explicit type conversion not allowed from "bytes32" to "string storage pointer"
        bytes memory bytesArray = new bytes(65);
        for (uint256 i; i < 32; i++) {
            bytesArray[i] = _a[i];
        }
		for (uint256 i=32; i < 64; i++) {
            bytesArray[i] = _b[i-32];
        }
		bytes32 b;
		assembly  { mstore(add(b,32),_c)}
		bytesArray[64] = b[31];
		console.logBytes32( _a);
		console.logBytes32( _b);
		console.logUint(_c);
		console.logBytes(bytesArray);
        return bytesArray;
}
1 Like

I’m not sure I understand. Are you suggesting that ERC20Permit should use SignatureChecker?

1 Like

@frangio how you can verify or implement permit function when the wallet is a smart contract wallet like Argent???

Additional i found two errors, and i fixed, and finally it’s work

      function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
        // solhint-disable-next-line not-rely-on-time
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

        bytes32 structHash = keccak256(
            abi.encode(
                _PERMIT_TYPEHASH,
                owner,
                spender,
                value,
                _useNonce(owner),
                deadline
            )
        );

        bytes32 hash = _hashTypedDataV4(structHash);
		bytes memory signature = rsvToSig(r,s,v);

        // address signer = ECDSAUpgradeable.recover(hash, v, r, s);
		if (AddressUpgradeable.isContract(owner)) {
            try IERC1271Upgradeable(owner).isValidSignature(hash, signature) returns (bytes4 magicValue) {
                require(magicValue == IERC1271Upgradeable(owner).isValidSignature.selector , "ERC20Permit: invalid signature ");
            } catch {
                revert("ERC20Permit: invalid signature");
            }
        } else {
            require(ECDSAUpgradeable.recover(hash, signature) == owner , "ERC20Permit: invalid signature");
        }

        _approve(owner, spender, value);
    }

and for rsvtoSign

	 /* Function work around for reassembly the signature  */
    function rsvToSig(bytes32 _a, bytes32 _b, uint8 _c) public pure returns (bytes memory){
        bytes memory bytesArray = new bytes(65);
        for (uint256 i; i < 32; i++) {
            bytesArray[i] = _a[i];
        }
		for (uint256 i=32; i < 64; i++) {
            bytesArray[i] = _b[i-32];
        }
		bytesArray[64] = bytes1(_c);
        return bytesArray;
    }

All work very well in the Unit-Test, i need to verify with Smart Contract Wallet like Argent , but I feel confident that it will work

Additional when the auditor respond and publish the ERC20 Smart Contract Code, i will share here!!

I currently don't think we should add EIP1271 support in our ERC20Permit contract because EIP-2612 very clearly says:

a call to permit(owner, spender, value, deadline, v, r, s) will [set approval to value] if and only if:

  • [...]
  • r , s and v is a valid secp256k1 signature from owner of the message

Adding support for EIP 1271 invalidates the last condition.

You're free to add EIP 1271 support in your own contract, understanding that it somehow goes against the EIP and may have unintended consequences.

4 Likes