[dApp] 진짜 쉬운 이더리움 은행 만들기 2편 with Truffle

in ethereum •  7 years ago  (edited)

Ethereum_Truffle

할 것


  1. Contract 생성 및 작성

  2. MigrationDeploy

  3. 실행하기

이 프로젝트는 github에 등록되어 있습니다.


컨트랙트 생성


> truffle create contract Bank



잘 생성되었나 확인합시다:

> tree
.
├── contracts
│   ├── Bank.sol
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
├── truffle-config.js
└── truffle.js

3 directories, 5 files



/contractsBank.sol 이 보입니다.

생성자 및 멤버 변수


pragma solidity ^0.4.20;

contract Bank {
  function Bank() {
    // 생성자
  }
}



Bank.sol 컨트랙트와 함께 생성자(Constructor)가 자동으로 생성되었습니다.

첫번째 라인은 0.4.2 ~ 0.5.0 버전 사이의 Solidity Compiler 를 사용함을 의미합니다. truffle version 을 입력하여 확인해볼까요?

> truffle version
Truffle v4.1.8 (core: 4.1.8)
Solidity v0.4.23 (solc-js)



멤버 변수를 선언합니다:

contract Bank {
  // 계좌의 소유주
  address public owner;
  ...


  • address: 20byte 사이즈의 이더리움 주소를 담는 특별한 타입입니다.

멤버 변수를 선언하고 Visibility 를 정의합니다. Solidity 는 객체 지향 프로그래밍 언어에서 흔히 사용하는 접근지정자(Access Modifier)를 Visibility 로 부릅니다. public 으로 설정하면 다른 Contract 와 클라이언트가 이 변수를 들여다 볼 수 있습니다. 자세한 내용은 여기에서 확인해주세요.

이제 생성자(Constructor)를 수정합시다:

function Bank(address _owner) public {
        owner = _owner;
}



이 생성자는 address 타입의 _owner 매개 변수를 받아 계좌 소유주를 의미하는 owner 멤버 변수에 대입합니다.

입금 함수


function deposit() public payable {
        require(msg.value > 0);
}


  • payable 은 매우 중요합니다. 이 키워드가 없다면 이더리움 트랜젝션(Transaction)이 불가능합니다. deposit() 함수는 직접적으로 이더리움을 전송하므로 만약 payable 키워드 없이 함수를 정의하면 트랜잭션(Transaction)은 거절(reject)됩니다.

  • deposit() 함수는 payable function 이기 때문에 내부에 송금 로직을 작성하지 않고도 이더리움을 전송 할 수 있습니다.

  • require 은 괄호 안의 값을 평가합니다. 만약 이라면 계속해서 함수를 실행 시키지만 거짓 일땐 함수 실행을 중지시키며 Transaction 을 취소(Revert)합니다. msg.value > 0 은 입금(deposit) 금액이 0 ETH 이상인지 검사합니다.

  • msg.value 는 전송하는 이더리움의 양입니다. Bank.sol 어디에서 정의되어 있지 않지만 사용 가능한 이유는 우리가 payable 함수를 실행(call)시킬때 인자로 msg 객체를 전달합니다.

출금 함수


function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
}


  • require 를 이용해 withdraw() 함수를 실행(call)하는 클라이언트(msg.sender)가 계좌 소유주(owner)와 동일한지 확인합니다. msg.sender 는 함수를 실행하는 주체(address)입니다. 값은 함수 실행시 자동 할당됩니다.

  • transfer(amount) 함수는 amount 만큼 이더리움을 송금합니다. 이는 payable function call 과 달리 transaction 이 아닙니다. 이에 대한 자세한 내용은 여기를 참고하세요.

  • address(this).balance 는 컨트랙트의 이더 잔액(balance)입니다. address(this)Contract 인스턴스의 주소를 의미합니다. 즉 thisContract 자기 자신입니다.

정리하자면 withdraw() 는 함수를 실행시키는 클라이언트가 계좌의 소유주임을 확인하고 현재 Contract 의 잔액(balance) 만큼 owner 에세 송금합니다.

마지막으로 Bank.sol 의 전체 코드를 확인하세요.

pragma solidity ^0.4.4;

contract Bank {
    address public owner;

    function Bank(address _owner) public {
        owner = _owner;
    }

    function deposit() public payable {
        require(msg.value > 0);
    }

    function withdraw() public {
        require(msg.sender == owner);
        owner.transfer(address(this).balance);
    }
}



축하합니다! 이제 Contract 모두 작성했습니다. 앞으로

  • compile

  • migrate

작업이 남았습니다!


Compile & Migrate



컴파일을 합니다:

> truffle compile
Compiling ./contracts/Bank.sol...
Compilation warnings encountered:
Writing artifacts to ./build/contracts

/build 폴더에 여러분이 작성한 Bank.sol 코드가 Bank.json 으로 아름답게 변환되어 있을겁니다.

Truffle 은 여러분이 작성한 Contract 를 어떻게 Deploy 할 지 아직 모릅니다. 이를 알려주기 위해서 우리는 Migration Script 를 작성합니다.

> truffle create migration bank



/migrations 폴더에 1525834979_bank.js 처럼 타임스탬프(timestamp) 형식으로 파일이 자동 생성되었습니다.

아래 코드로 채워주세요:

var Bank = artifacts.require("Bank");

module.exports = function(deployer) {
  let ownerAddress = web3.eth.accounts[0];
  deployer.deploy(Bank, ownerAddress);
};



우리가 CompileBank.json 를 불러오기 위해 artifacts.require 라는 조금 특수한 구문을 사용합니다. 이는 TruffleContract 를 성공적으로 Deploy 하도록 고안 방법입니다.

  • ownerAddress0 번 계정을 대입했습니다.

  • web3.eth.accounts 는 클라이언트의 정보를 담고 있는 배열(Array)입니다. 즉 web3.eth.accounts[0]Ganache 상의 첫번째 계정과 동일하며 ownerAddress 는 이를 담고 있습니다.

  • deplyer.deployContractDeploy 합니다. 첫 번째 매개 변수는 Contract 를 전달하고 두 번째 매개 변수는 생성자(Constructor)에 전달할 매개 변수입니다. 우리가 앞서 작성한 생성자 함수에는 address 타입의 매개 변수를 요구하므로 ownerAddress 를 전달합니다. 이는 우리가 작성한 은행의 소유주는 Ganache 의 첫 번째 계정, 다시 말해 web3.eth.accounts[0] 이라는걸 뜻합니다.

Migration 하기 전에 마지막으로 설치한 Ganache 를 실행시켜주세요. Ganache 인스턴스가 가동중이 아니라면 Migration 이 불가능합니다.

Ganache 실행 화면:

Ganache

이제 터미널에 truffle migrate 를 입력해주세요:

> truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
  Replacing Migrations...
  ... 0x189d93748c4f08398e841a303017514df868b7fcb54eba73bb4a631aad8c2bb9
  Migrations: 0x695ebfa99c04c3bcf934c65dd84cafa8d7e955f9
Saving successful migration to network...
  ... 0x0b1a2c70362deeb97322306c1680e865bd7759b54aba69ae57d8f51985d9e493
Saving artifacts...
Running migration: 1525834979_bank.js
  Replacing Bank...
  ... 0x9f702f9405c93be4c08b8dded6e3ce9f682228d9d54456fc331766270671132d
  Bank: 0xc18d5daf5afab9bc329d81700b269d4aac25a528
Saving successful migration to network...
  ... 0x84af0d76df1ecc280f6624f8a91691ebde7c646d3fa45d903a00569a1afb1454
Saving artifacts...

만약 에러가 발생하면 truffle migrate --reset 를 입력해주세요. 이전에 작업하시던 dApp 프로젝트와 충돌이 날 때가 있습니다.


실습



우리는 프론트엔드 없이 콘솔 환경 실습합니다.

> truffle console
truffle(development)>



이제 DeployContract 의 인스턴스를 변수에 저장합니다:

truffle(development)> Bank.deployed().then(instance => bank = instance)



Bank.deployed()Contract 의 인스턴스 평가하는 프로미스(Promise)를 반환합니다. then 을 이용하여 우리는 이를 bankbind 합니다.

owner 멤버 변수를 확인해봅시다:

truffle(development)> bank.owner()
'0xdaf72fcee99c3ed561b5f91a83b69c6f3d6b02e8'



Ganache 의 첫 번째 계정과 address 가 동일합니다!

이제 은행에 10 ETH 만큼 입금을 해봅시다:

truffle(development)> bank.deposit({value: web3.toWei(10, 'ether')})



depositpayable 함수입니다. 때문에 저희는 value 를 매개 변수로 담을 수 있습니다. valueETH 의 최소 단위인 Wei 를 가집니다. 따라서 web3.toWei 를 통해 10 ETHWei 로 변환해야합니다.

이제 Ganache 의 첫 번째 계정의 잔액(balance)을 확인합니다:

Ganache_First_Account

89.94 ETH 가 남았습니다. 왜 90.00 ETH 가 아닐까요? 이는 Contract 의 함수를 실행시키는 수수료(Gas) 때문입니다. Gas 에 대한 자세한 내용은 여기를 참고하세요.

이제 출금할 차례입니다:

truffle(development)> bank.withdraw()



Ganache_First_Account

첫 번째 계정에 10 ETH 가 다시 들어왔습니다!

마치며



축하합니다! 드디어 이더리움 은행 dApp 을 완성했습니다. 여기까지 따라오신 분들 모두 수고하셨습니다. 감사합니다.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

안녕하세요. 손당근님
저는 이제 막 dapp 개발 공부를 시작한 모도리라고 합니다.
최신 자료가 많이 없었는데, 이렇게 포스팅 해주셔서 감사합니다.
따라하면서 한 가지 궁금한 점이 있었는데요.
msg.sender는 무조건 account[0]로 고정되어 있는건가요? 혹시나 해서 bank.deposit({from: account[1]의 주소, value: web3.toWei(10, 'ether')}) 이런식으로 실행을 해보려니 invalid address 에러나 나오네요. ganache를 이용해서 테스트해 볼때에는 컨트랙트 owner만 msg를 보낼 수 있는지 궁금합니다.

좋은 글 감사합니다.

자세하고 쉬운 설명 감사합니다~ 잘 보았어요~