[모각소] 지갑 스마트 컨트랙트

장성호·2022년 7월 13일
0

[모각소]

목록 보기
2/16

이더리움 스마트 컨트랙트를 활용해서, 간단한 기능을 가지는 지갑을 만들어보는 과제가 주어졌다. 사실 강의에서 과제에 대한 코드가 제시되지만, 이 코드를 보기 전에 스스로 만들어보고 강의를 진행하는 것을 추천받았다. 잘 만들 수 있을까 걱정이 앞서지만 일단 도전해보자. 요구사항은 다음과 같다.

  1. 입금은 누구나 가능하다.
  2. Owner는 자금을 무제한 인출 가능하다.
  3. Non-owner는 특정 주소의 특정 금액만 인출 가능하다.
  4. Owner만 Non-owner의 접근 권한을 바꿀 수 있다.

MyWallet

MyWallet.sol

pragma solidity 0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol";

import "./Owned.sol";

/*
    1. 입금은 누구나 가능하다.
    2. Owner는 자금을 무제한 인출 가능하다.
    3. Non-owner는 특정 주소의 특정 금액만 인출 가능하다.
    4. Owner만 Non-owner의 접근 권한을 바꿀 수 있다.
*/

contract MyWallet is Owned {

    using SafeMath for uint;

    mapping(address => uint) public Balances;

    event DepositEvent(address _addr, uint _amount);
    event WithdrawEvent(address _addr, uint _amount);

    function getSmartContractBalance() public view returns(uint) {
        return address(this).balance;
    }

    function deposit(uint _amount) public payable {
        require(address(msg.sender).balance >= _amount, "Not enough ether");
        Balances[msg.sender] = Balances[msg.sender].add(_amount);

        emit DepositEvent(msg.sender, _amount);
    }

    function withdrawalTo(address payable _to, uint _amount) public payable {
        require(Balances[msg.sender] >= _amount, "Not enough ether");
        Balances[msg.sender] =  Balances[msg.sender].sub(_amount);
        Balances[_to] =   Balances[_to].add(_amount);

        emit WithdrawEvent(msg.sender, _amount);
        emit DepositEvent(_to, _amount);

        _to.transfer(Balances[msg.sender]);
    }

    receive() external payable {
        deposit(msg.value);
    }

    fallback () external {
        
    }
}

Owned.sol

pragma solidity ^0.8.0;

contract Owned {
    address owner;

    constructor() public {
        owner = msg.sender;
    }
    
    /*
        제어자.
        밑줄 부분에 해당 제어자를 사용하는 함수 본문을 복사해온다.
        그리고 제어자의 내용을 포함하여 다시 해당 함수로 복사한다.
        너무 남용하면 코드가 복잡해지므로 주의한다.
    */
    modifier onlyOwner() {
        require(msg.sender == owner, "You are not allowed");
        _;
    }

}

스스로 지갑 스마트 컨트랙트를 만들어 보는게 쉽지 않았다. 왜냐면 2번, 4번 요구사항이 이해가 안 됐기 때문이다. 이유를 생각해보니 스마트 컨트랙트를 배포한 사람의 지갑 주소와 배포된 스마트 컨트랙트의 주소가 다르다는 것부터 잘못 이해하고 있었다. 그리고 지갑 스마트 컨트랙트를 만든다는 것은 이 지갑을 이용하는 사람마다 스마트 컨트랙트가 배포된다는 점도 이해하지 못 하고 있었다.

deposit()과 receive()

function deposit(uint _amount) public payable {
	require(address(msg.sender).balance >= _amount, "Not enough ether");
	Balances[msg.sender] = Balances[msg.sender].add(_amount);

	emit DepositEvent(msg.sender, _amount);
 }
 
 ...
 
 receive() external payable {
	deposit(msg.value);
 }

그렇다보니 receive() 함수를 이용해서 스마트 컨트랙트에 이더리움을 입금 받는 단계에서 더이상 진행할 수가 없었다. receive()를 통해 받은 이더리움을 Balances[msg.sender]에 넣어서 관리하려고 했는데, 이렇게 되면 송신한 이더리움은 스마트 컨트랙트 자체에도 저장되고 Balances[msg.sender]에도 저장되었다. 즉 이중 지불 문제가 발생한 것이다. 그렇다고 Balances[msg.sender]에 저장하지 않으면, 입금된 이더리움을 활용할 수단이 없었다.

SimpleWallet

SimpleWallet.sol

// solidity-ethereum-bootcamp with Thomas Wiesner

pragma solidity 0.8.0;

import './Allowance.sol';

contract SimpleWallet is Allowance {

    event MoneySent(address indexed _beneficiary, uint indexed _amount);
    event MoneyReceive(address indexed _from, uint indexed _amount);

    function withdrawMoney(address payable _to, uint _amount) public ownerOrAllowed(_amount) {
        require(address(this).balance >= _amount, "There are not enough funds stored in the smart contract");
        if(!isOwner()) {
            reduceAllowance(msg.sender, _amount);
        }
        MoneySent(_to, _amount);

        _to.transfer(_amount);
    }

    /*
        public, view, onlyOwner와 같은 함수의 키워드 순서는 상관없다. 
    */
    function renounceOwnership() public view override(Ownable) onlyOwner {
        revert("Can't renouce ownership here.");
    }

    receive() external payable {
        emit MoneyReceive(msg.sender, msg.value);
    }

    fallback () external {
        
    }
}

Allowance.sol

// solidity-ethereum-bootcamp with Thomas Wiesner

pragma solidity 0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol";

contract Allowance is Ownable { 
    using SafeMath for uint;

    event AllwanceChanged(address indexed _forWho, address indexed _fromWhom, uint _oldAmount, uint _newAmount);

    mapping(address => uint) public allowance;

    function isOwner() internal view returns(bool) {
        return owner() == _msgSender();
    }

    function getBalance() public view returns(uint) {
        return address(this).balance;
    }

    function addAllowance(address _who, uint _amount) public onlyOwner {
        emit AllwanceChanged(_who, msg.sender, allowance[_who], _amount);
        allowance[_who] = _amount;
    }

    modifier ownerOrAllowed(uint _amount) {
        require(isOwner() || allowance[msg.sender] >= _amount, "You are not allowed");
        _;
    }

    function reduceAllowance(address _who, uint _amount) internal {
        emit AllwanceChanged(_who, msg.sender, allowance[_who], allowance[_who] - _amount);
        allowance[_who] = allowance[_who].sub(_amount);
    }
}

강의에서 제공되는 코드를 하나하나 따라가다보니, "지갑 스마트 컨트랙트를 만든다는 것은 이 지갑을 이용하는 사람마다 스마트 컨트랙트가 배포된다"라는 점을 기본으로 하고 있었다. 출금하고자 하는 금액을 스마트 컨트랙트 내의 잔고와 비교하는 것을 보고 깨달았다. 스마트 컨트랙트 내 입금된 금액을 따로 관리해야하는 줄 알았는데, adderss(this).balance를 통해 관리하면 되었다.

그동안 강의를 수강하면서 나름 많은 예시 코드를 따라하고 있었지만, 계속 와닿지 않던 이유가 앞서 굵은 글씨로 표기한 내용을 이해하지 못 해서 그런 것 같았다. 이번에 주어진 과제를 스스로 해보고, 강사님이 제공해주신 코드와 비교해보면서 번뜩 이해하지 못 하던 것이 무엇인지 깨달아서 다행이다.

Event와 log

[
	{
		"from": "0xa42b1378D1A84b153eB3e3838aE62870A67a40EA",
		"topic": "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0",
		"event": "OwnershipTransferred",
		"args": {
			"0": "0x0000000000000000000000000000000000000000",
			"1": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
			"previousOwner": "0x0000000000000000000000000000000000000000",
			"newOwner": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"
		}
	}
]

앞으로 스마트 컨트랙트와 웹 서로 상호작용하는 어플리케이션을 만들어나갈텐데, 무엇을 기반으로 상호작용하는지 그동안 잘 이해가 안 되었다. Event를 활용해서 상호작용한다고 했는데, 왜 Event를 작성해서 이런 log를 출력하는지가 와닿지 않았었다.

이번에 지갑 스마트 컨트랙트를 만들면서 다양한 Event를 출력해보니, 트랜잭션 사이트에서 log에 대한 이런 JSON을 긁어올 수 있을 것 같았다. 즉 웹 입장에서 스마트 컨트랙트 내부에서 무슨 일이 일어났는지 알 수 있을 것 같았다.

다음 모각소는?

다음에는 실제로 이러한 상호작용을 위한 웹을 만들어보는 단계이다. 웹 프로그래밍은 너무 오랜만이라 잘 할 수 있을지 모르겠다 😂 그동안 Flutter만 주구장창 해와서 HTML이랑 CSS은 대부분 잊었다. 많이 해본 적도 없긴 하지만... 그렇다고 가만히 있을 수는 없으니 달려보자!

profile
일벌리기 좋아하는 사람

2개의 댓글

comment-user-thumbnail
2022년 7월 22일

달리는 치타님 제 지갑은 왜안채워지나요

1개의 답글