ERC-20에서 재사용 가능한 형태의 Lock 함수

citron03·2022년 4월 6일
0

블록체인

목록 보기
16/19

재사용 가능한 Lock 함수 (ERC-20)

  • ERC-20 토큰에서 token transfer에 대한 Lock을 설정할 수 있다.
  • 의도치 않은 주소로 토큰을 잘못 보내는 경우가 있을 수 있고, 그에 대한 예방책으로 토큰 전송에 Lock을 걸어놓으면, Lock을 푼 주소로만 토큰을 전송할 수 있게 하는 것이다.
  • 토큰의 Lock에 사용할 변수를 stroage에 저장될 수 있도록 선언하도록 한다.
contract SimpleToken is ERC20Interface, OwnerHelper {
    bool public _tokenLock;
    mapping (address => bool) public _personalTokenLock;
    // 코드 생략...
}
  • 해당 컨트랙트의 모든 토큰 전송을 Lock하는 _tokenLock 변수와 개별 주소에 대한 Lock을 설정하는 _personalTokenLock 변수를 선언한다.

  • Lock을 다루는 함수를 다음과 같이 설정한다.

contract SimpleToken is ERC20Interface, OwnerHelper {

    constructor(string memory getName, string memory getSymbol) {
        _name = getName;
        _symbol = getSymbol;
        _decimals = 18;
        _totalSupply = 100000000e18;
        _balances[msg.sender] = _totalSupply;
        _tokenLock = true; // 초기값을 true로 설정한다.
    }
    
	// ... 코드 생략
    
    function isTokenLock(address from, address to) public view returns (bool lock) {
        lock = false;
        if(_tokenLock == true){
            lock = true;
        }
        if(_personalTokenLock[from] == true || _personalTokenLock[to] == true) {
            lock = true;
        }
    }

    function removeTokenLock() onlyOwner public {
        require(_tokenLock == true, "_tokenLock is already false");
        _tokenLock = false;
    }

	function removePersonalTokenLock(address _who) onlyOwner public {
        require(_personalTokenLock[_who] == true, "alredy lock false");
        _personalTokenLock[_who] = false;
    }

    function addTokenLock() onlyOwner public {
        require(_tokenLock == false, "_tokenLock is already true");
        _tokenLock = true;
    }

    function addPersonalTokenLock(address _who) onlyOwner public {
        require(_personalTokenLock[_who] == false, "alredy lock true");
        _personalTokenLock[_who] = true;
    }

    function isPersonalTokenLock(address _who) view public returns(bool) {
        return _personalTokenLock[_who];
    }
  • 솔리디티에서 boolean 변수의 초기값은 false이다.
  • 토큰 전체를 Lock할 수 있는 변수인 _tokenLock의 초기값을 생성자에서 true로 설정하여, 컨트랙트의 맨 처음 생성시 토큰의 전송은 Lock 된다.

🍇 isTokenLock

  • 만약 토큰이 전송될 수 있는 상태라면, false를 반환한다.
  • 토큰 전체(_tokenLock)가 Lock 되어있거나, 보내는 주소 또는 받는 주소 둘 중 하나라도 Lock되어 있을 시에도 true를 반환한다.

🍇 removeTokenLock

  • _tokenLock이 만약 잠겨(true)있다면, _tokenLock을 해제(false)한다.
  • 이 함수는 해당 컨트랙트의 주인(owner)만 실행할 수 있다.

🍇 removePersonalTokenLock

  • 주소를 인자로 받아 해당 주소의 _personalTokenLock이 잠금(true)되어 있다면, 해제(false)한다.
  • 이 함수는 해당 컨트랙트의 주인(owner)만 실행할 수 있다.

🍇 addTokenLock

  • 만약 _tokenLock이 해제(false)되어있다면, 잠근(true)다.
  • 이 함수는 해당 컨트랙트의 주인(owner)만 실행할 수 있다.

🍇 addPersonalTokenLock

  • 주소를 인자로 받아, 만약 해당 주소의 토큰의 Lock이 해제(false)되어 있다면, 잠근(true)다.
  • 이 함수는 해당 컨트랙트의 주인(owner)만 실행할 수 있다.

🍇 isPersonalTokenLock

  • 주소를 인자로 받아, 해당 주소의 개별 Lock(_personalTokenLock)이 잠겨있으면, true 잠겨있지 않다면, false를 반환한다.
  • 이 함수는 storage state를 읽지만, 상태를 수정하지 않는 view 함수이다.

전체 코드

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.10;

interface ERC20Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address spender, address recipient, uint256 amount) external returns (bool);
    
    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Transfer(address indexed spender, address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 oldAmount, uint256 amount);
}

library SafeMath {
  	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
		uint256 c = a * b;
		assert(a == 0 || c / a == b);
		return c;
  	}

  	function div(uint256 a, uint256 b) internal pure returns (uint256) {
	    uint256 c = a / b;
		return c;
  	}

  	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
		assert(b <= a);
		return a - b;
  	}

  	function add(uint256 a, uint256 b) internal pure returns (uint256) {
		uint256 c = a + b;
		assert(c >= a);
		return c;
	}
}

abstract contract OwnerHelper {
  	address private _owner;

  	event OwnershipTransferred(address indexed preOwner, address indexed nextOwner);

  	modifier onlyOwner {
		require(msg.sender == _owner, "OwnerHelper: caller is not owner");
		_;
  	}

  	constructor() {
            _owner = msg.sender;
  	}

       function owner() public view virtual returns (address) {
           return _owner;
       }

  	function transferOwnership(address newOwner) onlyOwner public {
            require(newOwner != _owner);
            require(newOwner != address(0x0));
            address preOwner = _owner;
    	    _owner = newOwner;
    	    emit OwnershipTransferred(preOwner, newOwner);
  	}
}

contract SimpleToken is ERC20Interface, OwnerHelper {

    using SafeMath for uint256;
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) public _allowances;

    uint256 public _totalSupply;
    string public _name;
    string public _symbol;
    uint8 public _decimals;
    bool public _tokenLock;
    mapping (address => bool) public _personalTokenLock;

    constructor(string memory getName, string memory getSymbol) {
        _name = getName;
        _symbol = getSymbol;
        _decimals = 18;
        _totalSupply = 100000000e18;
        _balances[msg.sender] = _totalSupply;
        _tokenLock = true;
    }
    
    function name() public view returns (string memory) {
        return _name;
    }
    
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    
    function decimals() public view returns (uint8) {
        return _decimals;
    }
    
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }
    
    function balanceOf(address account) external view virtual override returns (uint256) {
        return _balances[account];
    }
    
    function transfer(address recipient, uint amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
    
    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }
    
    function approve(address spender, uint amount) external virtual override returns (bool) {
        uint256 currentAllownace = _allowances[msg.sender][spender];
        require(currentAllownace >= amount, "ERC20: Transfer amount exceeds allowance");
        _approve(msg.sender, spender, currentAllownace, amount);
        return true;
    }
    
    function _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, currentAmount, amount);
    }

    function transferFrom(address sender, address recipient, uint256 amount) external virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        emit Transfer(msg.sender, sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, msg.sender, currentAllowance, currentAllowance - amount);
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        require(isTokenLock(sender, recipient) == false, "TokenLock: invalid token transfer");
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance.sub(amount);
        _balances[recipient] = _balances[recipient].add(amount);
    }
    

    function isTokenLock(address from, address to) public view returns (bool lock) {
        lock = false;
        if(_tokenLock == true){
            lock = true;
        }
        if(_personalTokenLock[from] == true || _personalTokenLock[to] == true) {
            lock = true;
        }
    }

    function removeTokenLock() onlyOwner public {
        require(_tokenLock == true, "_tokenLock is already false");
        _tokenLock = false;
    }

	function removePersonalTokenLock(address _who) onlyOwner public {
        require(_personalTokenLock[_who] == true, "alredy lock false");
        _personalTokenLock[_who] = false;
    }

    function addTokenLock() onlyOwner public {
        require(_tokenLock == false, "_tokenLock is already true");
        _tokenLock = true;
    }

    function addPersonalTokenLock(address _who) onlyOwner public {
        require(_personalTokenLock[_who] == false, "alredy lock true");
        _personalTokenLock[_who] = true;
    }

    function isPersonalTokenLock(address _who) view public returns(bool) {
        return _personalTokenLock[_who];
    }
}

컨트랙트 배포

  • 이 코드를 Ropsten 테스트 네트워크에 배포하여 테스트해 보았다. (remix를 사용하였다)

테스트

  • _personalTokenLock이 잘 작동하고, 재사용이 가능한지 확인한다.

  • 초기의 _personalTokenLock값은 false이다.

  • addPersonalTokenLock을 실행한 뒤, 다시 _personalTokenLock값을 확인한다.

  • addPersonalTokenLock을 실행한 트랜잭션이 처리 되었다.

  • 트랜잭션의 처리 후 다시 확인한 _personalTokenLock은 true가 되었다.

  • 이번엔 removePersonalTokenLock을 실행하고, 해당 트랜잭션의 처리를 기다린다.

  • 트랜잭션이 처리된 후, _personalTokenLock은 다시 false가 되었다.

추가적인 개선안

  • 처음 컨트랙트 생성시에 모든 개별 주소의 토큰을 Lock하는 방법을 생각해 보았다.

  • 처음에는 mapping (address => bool) public _personalTokenLock을 constructor를 통해서 true로 초기화하는 방법을 생각했으나, 모든 개별 주소를 컨트롤할 수 없다고 생각했다.

  • 따라서, 다시 생각한 것은 false일 때가 Lock이 된 상태로 만드는 것이다.

  • _personalTokenLock을 _personalTokenUnlock으로 바꾸면 자연스러울 것이다.

  • 그리고 isTokenLock 함수를 다음과 같이 수정한다.

    function isTokenLock(address from, address to) public view returns (bool lock) {
        lock = false;
        if(_tokenLock == true){
            lock = true;
        }
        if(_personalTokenUnlock[from] == false || _personalTokenUnlock[to] == false) {
            lock = true;
        }
    }

회고

  • 처음에 솔리디티 작성은 조금 어색하였으나, 예제 코드들을 보며 익숙해졌던 것 같다.

  • Lock의 재사용은 잘 구현된 것 같아 만족스럽다.

  • mapping을 이용하여 만든 변수는 사용이 익숙치 않아서 좀 헤매었던 부분이 있었다.

  • Lock 함수에 대해서 처음에 구현한 버전보다 조금 더 사용자 입장에서 도움이 될 수 있는 방향을 생각해보았다. (추가적인 개선안)

  • 이미 배포된 컨트랙트에 접근하는 방법은 생소했던 것 같다. (remix 또는 이더스캔)

  • 스마트 컨트랙트에 더 많은 사용자들이 쉽게 접근할 수 있도록 하는 클라이언트 앱에 대해서 생각하는 계기가 되었다.

  • 배포된 스마트 컨트랙트의 주소나 트랜잭션의 해시 등의 정보 중에서 중요한 것이 있다면 기록하는 것이 중요하다고 느꼈다.

  • 물론, 지갑에서 트랜잭션을 확인할 수 있지만, 시간이 지나면 과거의 기록에서 원하는 트랜잭션을 찾기 어려울 수도 있겠다고 느꼈다.

profile
🙌🙌🙌🙌

0개의 댓글