
할 것
Contract생성 및 작성Migration및Deploy실행하기
이 프로젝트는 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
/contracts 에 Bank.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인스턴스의 주소를 의미합니다. 즉this는Contract자기 자신입니다.
정리하자면 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 모두 작성했습니다. 앞으로
compilemigrate
작업이 남았습니다!
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);
};
우리가 Compile 한 Bank.json 를 불러오기 위해 artifacts.require 라는 조금 특수한 구문을 사용합니다. 이는 Truffle 이 Contract 를 성공적으로 Deploy 하도록 고안 방법입니다.
ownerAddress에0번 계정을 대입했습니다.web3.eth.accounts는 클라이언트의 정보를 담고 있는 배열(Array)입니다. 즉web3.eth.accounts[0]은Ganache상의 첫번째 계정과 동일하며ownerAddress는 이를 담고 있습니다.deplyer.deploy는Contract를Deploy합니다. 첫 번째 매개 변수는Contract를 전달하고 두 번째 매개 변수는 생성자(Constructor)에 전달할 매개 변수입니다. 우리가 앞서 작성한 생성자 함수에는address타입의 매개 변수를 요구하므로ownerAddress를 전달합니다. 이는 우리가 작성한 은행의 소유주는Ganache의 첫 번째 계정, 다시 말해web3.eth.accounts[0]이라는걸 뜻합니다.
Migration 하기 전에 마지막으로 설치한 Ganache 를 실행시켜주세요. Ganache 인스턴스가 가동중이 아니라면 Migration 이 불가능합니다.
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)>
이제 Deploy 한 Contract 의 인스턴스를 변수에 저장합니다:
truffle(development)> Bank.deployed().then(instance => bank = instance)
Bank.deployed() 는 Contract 의 인스턴스 평가하는 프로미스(Promise)를 반환합니다. then 을 이용하여 우리는 이를 bank 에 bind 합니다.
owner 멤버 변수를 확인해봅시다:
truffle(development)> bank.owner()
'0xdaf72fcee99c3ed561b5f91a83b69c6f3d6b02e8'
Ganache 의 첫 번째 계정과 address 가 동일합니다!
이제 은행에 10 ETH 만큼 입금을 해봅시다:
truffle(development)> bank.deposit({value: web3.toWei(10, 'ether')})
deposit 은 payable 함수입니다. 때문에 저희는 value 를 매개 변수로 담을 수 있습니다. value 는 ETH 의 최소 단위인 Wei 를 가집니다. 따라서 web3.toWei 를 통해 10 ETH 를 Wei 로 변환해야합니다.
이제 Ganache 의 첫 번째 계정의 잔액(balance)을 확인합니다:

89.94 ETH 가 남았습니다. 왜 90.00 ETH 가 아닐까요? 이는 Contract 의 함수를 실행시키는 수수료(Gas) 때문입니다. Gas 에 대한 자세한 내용은 여기를 참고하세요.
이제 출금할 차례입니다:
truffle(development)> bank.withdraw()

첫 번째 계정에 10 ETH 가 다시 들어왔습니다!
마치며
축하합니다! 드디어 이더리움 은행 dApp 을 완성했습니다. 여기까지 따라오신 분들 모두 수고하셨습니다. 감사합니다.
안녕하세요. 손당근님
저는 이제 막 dapp 개발 공부를 시작한 모도리라고 합니다.
최신 자료가 많이 없었는데, 이렇게 포스팅 해주셔서 감사합니다.
따라하면서 한 가지 궁금한 점이 있었는데요.
msg.sender는 무조건 account[0]로 고정되어 있는건가요? 혹시나 해서 bank.deposit({from: account[1]의 주소, value: web3.toWei(10, 'ether')}) 이런식으로 실행을 해보려니 invalid address 에러나 나오네요. ganache를 이용해서 테스트해 볼때에는 컨트랙트 owner만 msg를 보낼 수 있는지 궁금합니다.
좋은 글 감사합니다.
자세하고 쉬운 설명 감사합니다~ 잘 보았어요~