개요

ERC-20 표준에서 Token 소유자가 컨트랙트를 통해 자신이 소유한 Token을 다른 사람 또는 다른 컨트랙트로 전송하기 위해서

  1. transfer 함수를 실행하여 직접 전송을 하거나,
  2. 다른 사람이 자신의 Token을 전송 받을 수 있도록 허용하는 approve 함수를 실행하는 트랜잭션을 보낸다. 이 경우에는 전송 받을 사람이 다시 transferFrom 함수를 실행해야 한다.

만약 Token 소유자가 인출을 허용하였다는 것이 증명된다면

Token 소유자의 트랜잭션 없이도 전송 받는 사람 즉 수신자가 Token을 인출할 수 있게 되는데,

이러한 송금 방식이 ERC 표준에 있어서 조금 더 유연한 송금이 이루어지도록 할 것이다.

원리

Token 소유자는 자신이 수신자에게 Token을 인출토록 허용한다는 메시지에 서명한 뒤,

메시지를 Token 수신자에게 전송한다.

수신자는 소유자로부터 받은 서명을 이용하여 permit 이 구현되어 있는

ERC-20, ERC721 Token을 수신한다.

Open Zeppelin의 ERC-20 Permit

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.0;

interface IERC20Permit {
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function nonces(address owner) external view returns (uint256);

    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/extensions/ERC20Permit.sol)

pragma solidity ^0.8.0;

import "./IERC20Permit.sol";
import "../ERC20.sol";
import "../../../utils/cryptography/ECDSA.sol";
import "../../../utils/cryptography/EIP712.sol";
import "../../../utils/Counters.sol";

abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
    using Counters for Counters.Counter;

    mapping(address => Counters.Counter) private _nonces;

    bytes32 private constant _PERMIT_TYPEHASH =
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");

    bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;

    constructor(string memory name) EIP712(name, "1") {}

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual override {
        require(block.timestamp <= deadline, "ERC20Permit: expired deadline");

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

        bytes32 hash = _hashTypedDataV4(structHash);

        address signer = ECDSA.recover(hash, v, r, s);
        require(signer == owner, "ERC20Permit: invalid signature");

        _approve(owner, spender, value);
    }

    function nonces(address owner) public view virtual override returns (uint256) {
        return _nonces[owner].current();
    }

    function DOMAIN_SEPARATOR() external view override returns (bytes32) {
        return _domainSeparatorV4();
    }

    function _useNonce(address owner) internal virtual returns (uint256 current) {
        Counters.Counter storage nonce = _nonces[owner];
        current = nonce.current();
        nonce.increment();
    }
}