[멋쟁이 사자처럼 블록체인 스쿨 3기] 23-05-25

임형석·2023년 5월 25일
0

Soliditiy


ERC-20

ERC-20 은 Ethereum Request for Comment 20 의 약자로,

이더리움 네트워크의 개선을 제안하는 EIPs(Ethereum Improvement Proposals) 에서 관리하는 공식 프로토콜이다.

ERC-20 은 이더리움 네트워크에서 정한 표준 토큰 스펙이다.

ERC-20 토큰은 스마트 컨트랙트를 통해 생성되고, 이더리움 지갑으로 전송이 가능하다.

ERC-20 코드는 오픈제플린(Openzepplin) 이라는 솔리디티 프레임워크에서

쉽게 가져와서 사용할 수 있다.

openzepplin ERC-20

중요한 코드를 살펴보자.

    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

먼저, 맵핑으로 address => uint256 값을 나타내는 balances.
그리고, 이중맵핑으로 address => address => uint256 값을 나타내는 allowances 가 있다.

왜 allowances 라는 맵핑이 존재할까?

ERC-20 스마트 컨트랙트 코드 내에서 존재하는 "권한" 시스템이다.

예를 들면,

1이 2에게 100원을 송금하고 싶을때, 1은 이 거래를 중계해주는 3이라는 사람에게 자신의 지갑에 있는 100원만큼을 제어할 수 있는 권한을 주면,

권한을 가진 3은 1의 지갑에 있는 100원을 제어해 2에게 송금을 할 수 있다.

그리고 _approve 함수로 이러한 권한을 줄 수 있다.

approve

    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

함수의 아래 두번째 줄을 보면,

owner = 권한을 위임하려는 주소.
spender = 권한을 받는 주소.
amount = 얼마만큼의 권한을 가지는지.

즉, 이 함수를 호출하는 msg.sender 가 spender 에게 amount 금액 만큼을 제어할 수 있도록 권한을 주는 함수이다.


increase allowance

금액을 제어하는 권한을 늘릴 수도 있다.

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

이 함수는, 권한을 늘리는 함수이다. input 값으로 spender, addedvalue가 들어간다.

함수의 중간에 _approve 함수를 실행해 spender 에게 원래의 allowance 값 + addedvalue 만큼을 제어할 권한을 부여한다.


decrease allowance

반대로 권한을 줄일 수도 있다.

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

반대로, 권한을 감소시키는 함수이다. input 값으로 spender, substractvalue가 들어간다.

현재 allowance 값을 가져오고.(allowance 함수와 이중맵핑 _allowance 사용)

require 로 현재 allowance 값이 줄일 allowance 값보다 커야한다는 조건을 걸어준다.

그리고 _approve 함수를 불러와 제어할 수 있는 권한 값을 다시 설정해준다.


transfer , transferFrom , mint , burn

나머지 함수들은 토큰을 전송하거나, 발행, 제거하는 함수들이다.

다른 함수들은 특별히 어려운 내용이 없지만, transferFrom 함수에는

권한에 대한 코드가 나오므로 위에서 길게 적어보았다..

    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

transferFrom 함수는, 위에서 예시로 든 권한을 받은 3번이 사용하는 함수라고 보면 되겠다.

3이 1에게 100원 만큼을 제어할 수 있는 allowance 를 받았고,

allowance 를 사용하여 2에게 100원을 보낼때 이 함수를 사용하는 것.

중간에 적힌 _spendAllowance 함수는, 3이 1에게 받은 100원을 제어할 수 있는 권한을 제거하는 것이다.

권한을 제거한 후, _transfer 함수를 사용해 1의 지갑에서 2에게 100원을 전송해준다.

이렇게 권한을 주었다면, 다시 권한을 회수해 와야한다.

만약 송금을 했는데도 권한을 회수하지 않는다면, 3은 1의 토큰을 마음껏 사용할 수 있을 것이다.


msg.sender

msg.sender 의 경우 메시지 발신자의 주소를 호출한다.

그리고 msg.sender 를 호출하는 컨트랙트와 함수를 만들었다.

contract MSGSENDER {
    function A() public view returns(address) {
        address a = msg.sender;
        return a;
    }

    function B() public view returns(address) {
        address b = A();
        return b;
    }
}

위와 같이 같은 컨트랙트 내에 msg.sender 를 반환하는 함수 A를 만들고,

다른 함수 B 에서 A 함수를 호출하면 누가 msg.sender 일까?

1. 함수를 실행한 나
2. 함수 B
3. contract MSGSENDER

정답은 '함수를 실행한 나' 이다.

같은 컨트랙트 내에 있는 함수를 호출하면, msg.sender 는 동일하게 실행한 사람이다.

그렇다면, MSGSENDER 컨트랙트를 다른 컨트랙트에서 인스턴스화 시켜서

A 함수를 실행하면 어떻게 될까?

contract MSGSENDER2 {
    MSGSENDER msgsender = new MSGSENDER();

    function C() public view returns(address) {
        address c = msgsender.A();
        return c;
    }

    function D() public view returns(address) {
        address d = msgsender.B();
        return d;
    }
}

함수 C, D 모두 컨트랙트 MSGSENDER2 의 주소를 반환한다.

다른 컨트랙트에서 호출하면, 호출한 컨트랙트가 msg.sender 가 되는 것이다.


변수 일부만 받기

함수내에 다른 함수를 가져와서 사용이 가능하다.

예를 들어, 실행하면 1,2,3 을 하나씩 반환하는 함수가 있다.

function numbers() public pure returns(uint, uint, uint) {
        return (1,2,3);
    }

이 함수를 다른함수에서 사용하면, 역시 1,2,3 을 반환할 것이다.

function numbers2() public view returns(uint, uint, uint) {
        (uint a, uint b, uint c) = numbers();

        return (a,b,c); // 1,2,3
    }

그런데 함수를 가져와서 사용할 때, 변수의 일부만 받고 싶을땐 어떻게 할까?

아래 코드처럼 작성하면 가능하다.

function numbers3() public view returns(uint, uint) {
        (uint a, , uint c) = numbers();
        return (a,c); // 1,3
    }

a 와 c 사이에 한칸을 비워버리면 중간의 2 변수는 받지 않을 수 있다.

0개의 댓글