이 컨트랙트는 오픈제플린에서 제공하는 ERC20 컨트랙트(링크)와 유사합니다. 차이점이 있다면 Permit과 메타트랜잭션 기능이 다를 것입니다.
메타트랜잭션은 블록체인에서 발생하는 트랜잭션을 대신해 수행되는 트랜잭션입니다. 이를 통해 사용자는 원래의 트랜잭션을 대신하여 다른 계정에서 트랜잭션을 수행할 수 있습니다. 예를 들어, 사용자가 특정 거래를 수행하기 위해 자신의 지갑에서 일정 금액의 가스 비용을 지불해야 할 때, 사용자는 대신 다른 지갑에서 가스 비용을 지불할 수 있는 메타트랜잭션을 생성할 수 있습니다. 이를 통해 사용자는 거래를 더 빠르고 효율적으로 처리할 수 있습니다.
UniswapV2에서 메타트랜잭션을 사용하면 사용자는 직접 트랜잭션을 생성하지 않아도 됩니다. 대신, UniswapV2의 자동 거래 시스템이 대신 트랜잭션을 생성하여 사용자의 지갑에서 거래를 수행합니다. 이를 통해 사용자는 거래를 더 빠르고 효율적으로 처리할 수 있습니다.
그리고 모든 유니스왑 V2 Pool 토큰은 Permit 기능을 통해 메타 트랜잭션 승인을 지원합니다. 따라서 풀 토큰과의 프로그래밍 상호 작용이 발생하기 전에 트랜잭션 승인을 차단할 필요가 없습니다.
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 주소 참조).
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에서 특정 토큰을 거래하기 위해 별도의 승인 트랜잭션을 보낼 필요 없이, 해당 토큰에 대한 승인 절차를 수행할 수 있습니다.
함수 | 설명 |
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)가 인출할 수 있는 토큰의 양을 설정하는 내부 함수 |
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
The Uniswap V3 Smart Contracts (0) | 2023.04.12 |
---|---|
유니스왑 V2 컨트랙트 리뷰에서 알아보는 디파이 개발자를 위한 5가지 팁과 요령 (0) | 2023.03.27 |
UniswapV2-core - UniswapV2Factory.sol 코드 분석 (0) | 2023.03.26 |
UniswapV2-core - UniswapV2Pair.sol 코드 분석 (0) | 2023.03.26 |
UniswapV2-periphery - UniswapV2Library.sol 코드 분석 (0) | 2023.03.17 |