상세 컨텐츠

본문 제목

UniswapV2-core - UniswapV2ERC20.sol 코드 분석

Blockchain/DeFi

by Yongari 2023. 3. 26. 22:53

본문

UniswapV2ERC20.sol 개요

이 컨트랙트는 오픈제플린에서 제공하는 ERC20 컨트랙트(링크)와 유사합니다. 차이점이 있다면 Permit과 메타트랜잭션 기능이 다를 것입니다.

 

유니스왑V2의 메타트랜잭션과 Permit

메타트랜잭션은 블록체인에서 발생하는 트랜잭션을 대신해 수행되는 트랜잭션입니다. 이를 통해 사용자는 원래의 트랜잭션을 대신하여 다른 계정에서 트랜잭션을 수행할 수 있습니다. 예를 들어, 사용자가 특정 거래를 수행하기 위해 자신의 지갑에서 일정 금액의 가스 비용을 지불해야 할 때, 사용자는 대신 다른 지갑에서 가스 비용을 지불할 수 있는 메타트랜잭션을 생성할 수 있습니다. 이를 통해 사용자는 거래를 더 빠르고 효율적으로 처리할 수 있습니다.

UniswapV2에서 메타트랜잭션을 사용하면 사용자는 직접 트랜잭션을 생성하지 않아도 됩니다. 대신, UniswapV2의 자동 거래 시스템이 대신 트랜잭션을 생성하여 사용자의 지갑에서 거래를 수행합니다. 이를 통해 사용자는 거래를 더 빠르고 효율적으로 처리할 수 있습니다.


그리고 모든 유니스왑 V2 Pool 토큰은 Permit 기능을 통해 메타 트랜잭션 승인을 지원합니다. 따라서 풀 토큰과의 프로그래밍 상호 작용이 발생하기 전에 트랜잭션 승인을 차단할 필요가 없습니다. 

더보기

Domain Separator

keccak256(
  abi.encode(
    keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
    keccak256(bytes(name)),
    keccak256(bytes('1')),
    chainId,
    address(this)
  )
);

이름은 항상 유니스왑 V2입니다(이름 참조).
체인아이디는 ERC-1344 체인아이디 연산자 코드에서 결정됩니다.
address(this)는 Pair의 주소입니다(Pair 주소 참조).

 

Permit Typehash

keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');`

 

ERC-712(링크)

순수 ERC-20 토큰 콘트랙트에서 소유자는 msg.sender를 사용하여 권한을 부여하는 함수를 직접 호출해야만 승인을 등록할 수 있습니다. 메타 승인을 사용하면 소유권과 권한 부여는 호출자(릴레이어라고도 함)가 함수에 전달한 서명에서 파생됩니다. 이더리움 개인 키로 데이터에 서명하는 것은 까다로운 작업일 수 있기 때문에, 유니스왑 V2는 광범위한 커뮤니티 지원을 받는 서명 표준인 ERC-712를 사용해 사용자 안전과 지갑 호환성을 보장합니다.  

 

Permit


UniswapV2에서는 Permit 기능을 사용하여 특정 토큰에 대한 승인 절차를 간소화할 수 있습니다. 이 기능은 ERC-20 토큰을 승인하는 데 필요한 단계를 간소화하여 사용자가 더 쉽게 거래를 수행할 수 있도록 합니다. 이를 통해 사용자는 UniswapV2에서 특정 토큰을 거래하기 위해 별도의 승인 트랜잭션을 보낼 필요 없이, 해당 토큰에 대한 승인 절차를 수행할 수 있습니다.

 

UniswapV2ERC20.sol 핵심함수

 

함수 설명
totalSupply() 현재 총 발행량을 반환하는 함수
balanceOf(address account) 특정 주소의 잔액을 반환하는 함수
transfer(address recipient, uint256 amount) 특정 주소로 토큰을 전송하는 함수
allowance(address owner, address spender) 특정 주소(owner)가 다른 주소(spender)에게 인출을 허용한 토큰 양을 반환하는 함수
approve(address spender, uint256 amount) 다른 주소(spender)가 인출할 수 있는 토큰의 양을 설정하는 함수
transferFrom(address sender, address recipient, uint256 amount) 특정 주소(sender)로부터 다른 주소(recipient)로 토큰을 전송하는 함수로, 이 함수는 sender가 spender로부터 인출을 허용한 양보다 작거나 같은 양만큼만 전송
_mint(address account, uint256 amount) 새로운 토큰을 발행하여 특정 주소(account)에 지급하는 함수입니다.
_burn(address account, uint256 amount): 특정 주소(account)의 토큰을 소각하는 함수
_approve(address owner, address spender, uint256 amount) 다른 주소(spender)가 인출할 수 있는 토큰의 양을 설정하는 내부 함수



UniswapV2ERC20.sol
코드 분석 

pragma solidity =0.5.16;

import './interfaces/IUniswapV2ERC20.sol';
import './libraries/SafeMath.sol';


/*
이 컨트랙트는 ERC-20 유동성 토큰을 구현합니다. 
오픈제플린 ERC-20 컨트랙트와 유사하므로 다른 부분인 허가 기능에 대해서만 설명하겠습니다.

 이더리움에서 트랜잭션에는 실제 돈에 해당하는 이더(ETH)가 필요합니다.
 ERC-20 토큰은 있지만 이더가 없다면 트랜잭션을 전송할 수 없으므로 아무것도 할 수 없습니다. 
 이 문제를 방지하는 한 가지 해결책은 메타 트랜잭션입니다. 
 토큰 소유자는 다른 사람이 체인 외부에서 토큰을 인출할 수 있도록 허용하는 트랜잭션에 서명하고 인터넷을 사용하여 수신자에게 전송합니다. 
 그러면 ETH를 보유한 수신자가 소유자를 대신하여 허가서를 제출합니다.

 */
contract UniswapV2ERC20 is IUniswapV2ERC20 {
    using SafeMath for uint; // SafeMath 라이브러리를 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);

//체인 식별자를 검색하는 코드입니다. 이 코드는 Yul이라는 EVM 어셈블리 방언을 사용합니다. 현재 버전의 Yul에서는 chainid가 아닌 chainid()를 사용해야 한다는 점에 유의하세요.
    constructor() public {
        uint chainId;
        assembly {
            chainId := chainid
        }
//EIP-712에 대한 도메인 구분 기호 계산.
        DOMAIN_SEPARATOR = keccak256(
            abi.encode(
                keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
                keccak256(bytes(name)),
                keccak256(bytes('1')),
                chainId,
                address(this)
            )
        );
    }
    //새로운 토큰을 발행하여 특정 주소(account)에 지급하는 함수입니다.
    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);
    }

    //다른 주소(spender)가 인출할 수 있는 토큰의 양을 설정하는 함수입니다.
    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);
    }

    //다른 주소(spender)가 인출할 수 있는 토큰의 양을 설정하는 함수입니다.
    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;
    }

    //특정 주소(sender)로부터 다른 주소(recipient)로 토큰을 전송하는 함수로, 이 함수는 sender가 spender로부터 인출을 허용한 양보다 작거나 같은 양만큼만 전송할 수 있습니다.
    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;
    }

//이 함수는 권한을 구현하는 함수입니다. 이 함수는 관련 필드와 서명에 대한 세 개의 스칼라 값(v, r, s)을 매개변수로 받습니다.
    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
        //마감일 이후에는 거래를 수락하지 마세요.
        //abi.encodePacked(...)는 우리가 받을 것으로 예상되는 메시지입니다. 논스가 무엇인지 알고 있으므로 매개변수로 가져올 필요가 없습니다.
        bytes32 digest = keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );
//이더리움 서명 알고리즘은 서명에 256비트가 필요할 것으로 예상하므로 keccak256 해시 함수를 사용합니다.
//다이제스트와 서명에서 ecrecover를 사용하여 서명한 주소를 얻을 수 있습니다.
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
        _approve(owner, spender, value);
    }
}

 

출처:https://ethereum.org/en/developers/tutorials/uniswap-v2-annotated-code/#uniswapv2erc20

 

Home | ethereum.org

Ethereum is a global, decentralized platform for money and new kinds of applications. On Ethereum, you can write code that controls money, and build applications accessible anywhere in the world.

ethereum.org

 

관련글 더보기