- LP 토큰을 관리하는 컨트랙트이다.
- 표준 ERC-20 컨트랙트와 함수의 형태나 기능이 같으면 생략하고 새로운 내용만 기록한다.
pragma solidity =0.5.16;
import './interfaces/IUniswapV2ERC20.sol';
import './libraries/SafeMath.sol';
contract UniswapV2ERC20 is IUniswapV2ERC20 {
using SafeMath for uint;
string public constant name = 'Uniswap V2';
string public constant symbol = 'UNI-V2';
uint8 public constant decimals = 18;
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint) public nonces;
event Approval(address indexed owner, address indexed spender, uint value);
event Transfer(address indexed from, address indexed to, uint value);
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
- DOMAIN_SEPARATOR, PERMIT_TYPEHASH, nonce 는 모두 permit 을 위한 변수들이다. permit 은 소유자가 인출을 허용하였다는 것이 서명으로 증명된다면 Token 소유자의 트랜잭션 없이도 전송 받는 사람 즉 수신자가 Token 을 인출할 수 있게 되는 개념이다.
EIP-2612 : permit
- 정형화되고 구조화된 데이터 해싱과 서명에 대한 내용을 담고 있는 EIP712 에서는 “Definition of domainSeparator 에 대해 아래와 같이 설명한다.
domainSeparator = hashStruct(eip712Domain)
- eip712Domain 의 인자는 string name, string version, uint chainId, address verifyingContract, bytes32 salt 이다.
- name 은 사용자가 읽을 수 있는 DApp 또는 프로토콜의 이름이다.
- version 은 서명할 도메인의 현재 버전이다. 서로 다른 버전의 서명은 호환되지 않는다.
- chainId 는 EIP-155의 체인 ID 이다. 현재 활성 체인과 일치하지 않으면 서명을 거부해야 한다.
- verifyingContract 는 서명을 확인할 컨트랙트의 주소이며, 악의적으로 다른 컨트랙트 주소를 넣는 피싱을 예방할 수 있다.
- salt 는 임의의 값으로 domain separator 를 구분하는 수단이다.
- 프로토콜 설계자는 서명 도메인에 적합한 필드만 포함하면 되고, 사용하지 않는 필드는 구조체 유형에서 제외되지만, 순서는 위와 같아야 한다.
- PERMIT_TYPEHASH 는 Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline) 구조체 값에 keccak256 해시를 한 값이다. 구조체에서 nonce 는 해당 서명에 대한 재사용 공격을 방지하기 위한 것이며, deadline 은 서명의 유효기간을 설정하는 것이다.
- DOMAIN_SEPARATOR 는 UniswapV2ERC20 가 최초 배포될 때 constructor 에 의해 마치 상수값처럼 저장이 되어 고정된다.
function _mint(address to, uint value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _burn(address from, uint value) internal {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
}
function _approve(address owner, address spender, uint value) private {
allowance[owner][spender] = value;
emit Approval(owner, spender, value);
}
function _transfer(address from, address to, uint value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function approve(address spender, uint value) external returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
function transfer(address to, uint value) external returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
function transferFrom(address from, address to, uint value) external returns (bool) {
if (allowance[from][msg.sender] != uint(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}
_transfer(from, to, value);
return true;
}
- 위 함수들은 ERC-20 표준의 내용과 동일하기 때문에 생략한다.
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\\x19\\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
- owner 가 (owner, spender, value, nonce, deadline) 에 서명하고, v, r, s 값을 얻어 수신자에게 주면, 수신자는 permit() 함수를 실행하여 value 값을 수령하는 것이 EIP-2612 의 내용이다.
- permit() 함수가 실행되면 먼저 유효한 기간인지 확인하고, “owner 가 nonce 번째 전송을 하는데, 그 내용은 spender 에게 value 만큼을 deadline 안에 지급하는거다.” 라는 내용을 encode 하여 byte code 로 변환한 뒤 keccak256 으로 해싱을 한다.