[dApp] 진짜 쉬운 이더리움 은행 만들기 2편 with Truffle
할 것
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
모두 작성했습니다. 앞으로
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);
};
우리가 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를 보낼 수 있는지 궁금합니다.
좋은 글 감사합니다.
자세하고 쉬운 설명 감사합니다~ 잘 보았어요~