솔리디티(Solidity) 프로그래밍
in Dev on Blockchain, Solidity

Solidity is a contract-oriented, high-level language for implementing smart contracts.
개발환경 설정
Mist 브라우저
이더리움 지갑 프로그램
$ Ethereum-Wallet-win64-0-10-0>"Ethereum Wallet.exe" --rpc http://192.168.0.207:8545
Note: Insecure RPC connection
pragma solidity ^0.4.18;
contract MyFirstContract {
string message = "hello";
string goodbye = "잘가";
function sayHello() public view returns(string) {
return message;
}
function changeHello(string _message) public {
message = _message;
}
function sayGoodbye() public view returns(string) {
return goodbye;
}
function changeGoodbye(string _goodbye) public {
goodbye = _goodbye;
}
}
생성된 컨트랙트 확인
> eth.blockNumber
979
> eth.getBlock(814)
{
difficulty: 190874,
extraData: "0xd783010802846765746887676f312e392e34856c696e7578",
gasLimit: 60592358,
gasUsed: 366695,
hash: "0xa78e91b855732bc3877fde86cbb0e736a1798ff52dd55965a8eface4a8a1f9c8",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0xbfa12bc27bd8a01a77543a6094b8882f85a2dd58",
mixHash: "0xcda7642fe2e756abba6e6588e1704a8d9566bafcfa0d256f37b56b8998a064bb",
nonce: "0x2fd6feeabbb5dbae",
number: 814,
parentHash: "0x60c69c2ae4d79de3eb055c3f2e1b0a6e835d3245e86e9ea615dfd30aae627bc7",
receiptsRoot: "0x887d7f75d593561f074b2be61cee4a2b8d4ee97791e1b097560437cb8b44d0e7",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: 1926,
stateRoot: "0xb4bea3e5cea0fd39af2a94859e44194b737a8f793ba393f8296c7f152ae7f1cf",
timestamp: 1528090922,
totalDifficulty: 129787198,
transactions: ["0x010cc761723c9bec0797941b0d742e2601fc6bec4779b4c13493e46d506190a4"],
transactionsRoot: "0xeb7c53d52f6bc58c2387a91dcf6c4186428e33a5e8db0e26bd75ade8cffaef7d",
uncles: []
}
Remix IDE 개발환경
로컬 버전
browser-solidity-gh-pages/index.html
온라인 버전
http://remix.ethereum.org
- Environment : Web3 Provider
- Web3 Provider Endpoint -> http://192.168.0.207:8545
Account 에 계정 연결을 확인
- Compile
- Check “Auto compile”
- Settings
- Solidity version: 0.4.18+commit.9cf6e910
- Run
- Account 선택
- Value: 1 ether
- Deploy
- 콘솔에서 personal.unlockAccount(eth.accounts[0]) 수행
NodeJS
$ nodejs --version
v4.2.6
$ npm --version
3.5.2
MetaMast
- 로그인 패스워드: 1q2w3e4r
- Custom RPC
- New RPC URL: http://192.168.0.207:8545
Solc
$ sudo apt-get install solc
$ solc --version
solc, the solidity compiler commandline interface
Version: 0.4.24+commit.e67f0147.Linux.g++
$ which solc
/usr/bin/solc
Private 이더리움 네트워크 구축
Geth 초기화
$ mkdir smartcontract_testnet
$ cd smartcontract_testnet/
$ cp ../data_testnet/genesis.json .
$ geth --datadir /home/user01/smartcontract_testnet/ init /home/user01/smartcontract_testnet/genesis.json
Geth 실행
$ nohup geth --networkid 4649 --nodiscover --datadir /home/user01/smartcontract_testnet --rpc --rpcaddr "0.0.0.0" --rpcport 8545 --rpccorsdomain "*" --rpcapi "admin,db,eth,debug,miner,net,shh,txpool,personal,web3" --verbosity 6 2>>/home/user01/smartcontract_testnet/geth.log &
$ ps -eaf | grep geth
user01 1787 1650 0 10:39 pts/0 00:00:00 geth --networkid 4649 --nodiscover --datadir /home/user01/smartcontract_testnet --rpc --rpcaddr 0.0.0.0 --rpcport 8545 --rpccorsdomain * --rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3 --verbosity 6
$ kill $(ps aux | grep geth | grep rpc | awk '{print $2}')
Geth Console 연결
$ geth attach http://localhost:8545
Welcome to the Geth JavaScript console!
instance: Geth/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>
계정 생성
Welcome to the Geth JavaScript console!
instance: Geth/PrivateNetwork/v1.8.2-stable-b8b9f7f4/linux-amd64/go1.9.4
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> personal.newAccount("pass0")
"0xbfa12bc27bd8a01a77543a6094b8882f85a2dd58"
> personal.newAccount("pass1")
"0x7e0fc0342326a8645e1913771d42917c3a65c119"
> personal.newAccount("pass2")
"0x1cc0072199d86ba38b5fabf2235899d50cc0a29d"
> personal.newAccount("pass3")
"0x92ff3867275ab97c29785e88a5c7946ae9cfaa63"
채굴
> miner.start(1)
null
> eth.getBalance(eth.coinbase)
115000000000000000000
> web3.fromWei(eth.getBalance(eth.coinbase))
185
> miner.stop()
true
> eth.blockNumber
37
송금
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xbfa12bc27bd8a01a77543a6094b8882f85a2dd58
Passphrase:
true
> eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[1], value:web3.toWei(50, "ether")})
"0xbdb29bdf578ebcd1caa6f9b18aec1bf874ebed918e26f69b4f2b10dee2c2e33a"
> eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[2], value:web3.toWei(50, "ether")})
"0x7fb3141756d954a55d40f196c8ced6221a169e30ae11db8f2bae78feab47f09f"
> eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[3], value:web3.toWei(50, "ether")})
"0x46076f2fb46c9b6f221ea149066da79256aa50346b96142a3fd28b8e64a86254"
# 채굴되지 않은 트랜잭션 리스트
> eth.pendingTransactions
> eth.blockNumber
> eth.getBlock(43)
Peer 연결
$ sudo ifconfig enp0s8 192.168.0.207 netmask 255.255.255.0 up
[sudo] password for user01:
$ sudo route add default gw 192.168.0.1
$ ping 192.168.0.107
$ ifconfig -a
enp0s8 Link encap:Ethernet HWaddr 08:00:27:05:44:dc
inet addr:192.168.0.207 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::a00:27ff:fe05:44dc/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:710 errors:0 dropped:446 overruns:0 frame:0
TX packets:37 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:47898 (47.8 KB) TX bytes:2713 (2.7 KB)
Interrupt:16 Base address:0xd240
$ sudo mkdir -p /etc/sysconfig/network-scripts
$ sudo vi /etc/sysconfig/network-scripts/ifcfg-enp0s8
DEVICE=enp0s8
BOOTPROTO=static
BROADCAST=192.168.0.255
IPADDR=192.168.0.207
NETMASK=255.255.255.0
NETWORK=192.168.0.0
ONBOOT=yes
$ sudo vi /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=ubuntu
GATEWAY=192.168.0.1
> admin.nodeInfo.enode
"enode://33dbede907252cd274d95244b12a26157c716ea3c4131307e748191175a93afd0e500500829a30c30d5456f05c2f36e41cdaa09522d9916d13bc25e9b56bcdbd@[::]:30303?discport=0"
> admin.addPeer("enode://b128200b26a55a5aa2b605905b1dfa9be8426cf6d8f73c1a1c0ea89e4d4771a98feead21cc9830b772c559d42b91ee2455fad4402738571cad61f17c93b787ae@192.168.0.202:30303?discport=0")
> net.peerCount
0
> admin.peers
[]
솔리디티 문법
기본 자료형
함수
Web3
web3.js - Ethereum JavaScript API
- https://www.mobilefish.com/download/ethereum/web3api.html
- https://www.mobilefish.com/download/ethereum/DemoDapp.html
Express 설치
$ sudo npm install express-generator -g
[sudo] password for user01:
/usr/local/bin/express -> /usr/local/lib/node_modules/express-generator/bin/express-cli.js
/usr/local/lib
└─┬ express-generator@4.16.0
├── commander@2.13.0
├── ejs@2.5.7
├─┬ minimatch@3.0.4
│ └─┬ brace-expansion@1.1.11
│ ├── balanced-match@1.0.0
│ └── concat-map@0.0.1
├─┬ mkdirp@0.5.1
│ └── minimist@0.0.8
└── sorted-object@2.0.1
$ sudo ln -s /usr/bin/nodejs /usr/bin/node
$ express -h
Express 페이지 생성
$ express --view=pug web3
$ cd web3 && sudo npm install
$ DEBUG=web3 npm start
사이트 접속: http://192.168.0.207:3000/
- web3 demo
- /home/user01/web3/public
- js, monitor.html 업로드
- http://192.168.0.207:3000/monitor.html
- /home/user01/web3/public
Account
account.html
Wallet
wallet.html
Vote
vote.html
var vc = web3.eth.contract(
ABI
).at("contract address");
VoteContract.sol
> personal.unlockAccount(eth.accounts[0])
> miner.start()
스마트컨트랙트 개발 보안
Circuit Breaker 패턴
pragma solidity ^0.4.18;
contract CircuitBreakerDemo {
bool public stopped;
address public owner;
bytes16 public message;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier isStopped() {
require(!stopped);
_;
}
function CircuitBreakerDemo() public {
owner = msg.sender;
stopped = false;
}
function toggleCircuit(bool _stopped) public onlyOwner {
stopped = _stopped;
}
function message(bytes16 _message) public isStopped {
message = _message;
}
}
> var cb = eth.contract(abi).at('cb-address')
> cb
> cb.stopped()
false
> personal.unlockAccount(eth.accounts[0])
Unlock account 0xbfa12bc27bd8a01a77543a6094b8882f85a2dd58
Passphrase:
true
> cb.setMessage.sendTransaction("aaa", {from:eth.accounts[0], gas:500000})
"0x045569ec1f489e3d309b5a92556624e6e3694aa586e983268a6fb9c47ee34117"
> web3.toUtf8(cb.message())
"aaa"
> cb.toggleCircuit.sendTransaction(true, {from:eth.accounts[0], gas:500000})
"0xdc67f05731cbea80fc7b4f27cbe5f20b53d4098335b58258bbe56444875971aa"
> cb.setMessage.sendTransaction("ccc", {from:eth.accounts[0], gas:500000})
"0xce09f57acc6739c4520ab68b2167cd22d647edbf01a9d0450efda92ca27d12bf"
> web3.toUtf8(cb.message())
"bbb"
Mortal 패턴
- 더이상 필요하지 않게 된 계약을 그때그때 소멸시키는 패턴
- 소멸시킬 계약이 이더를 보유하고 있다면 이 계약으로부터 이더를 회수해야 함.
- 소멸과 동시에 이더를 계약의 소유자에게 송금
pragma solidity ^0.4.18;
contract Owned {
address public owner;
bytes16 public message;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function owned() internal {
owner = msg.sender;
}
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
}
contract Mortal is Owned {
function kill() public onlyOwner {
selfdestruct(owner);
}
}
contract MortalSample is Mortal {
string public somestate;
//fallback function
function() public payable {
}
function MortalSample() public {
owned();
somestate = "initial";
}
}
Access Restriction 패턴
pragma solidity ^0.4.18;
contract Owned {
address public owner;
bytes16 public message;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function owned() internal {
owner = msg.sender;
}
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
}
contract AccessRestrictionDemo is Owned {
string public somestate;
function AccessRestrictionDemo() public {
owned();
somestate = "initial";
}
function updateSomeState(string _newState) public onlyOwner {
somestate = _newState;
}
}
Withdraw 패턴
pragma solidity ^0.4.18;
contract Auction {
address public highhestBidder;
uint public highhestBid;
function Auction() public payable {
highhestBidder = msg.sender;
highhestBid = 0;
}
function bid() public payable {
require(msg.value > highhestBid);
uint refundAmount = highhestBid;
address currentHighhestBidder = highhestBidder;
highhestBid = msg.value;
highhestBidder = msg.sender;
if (currentHighhestBidder.send(refundAmount)) {
revert();
}
}
}
> var au = eth.contract([
]).at('0x9c98e7dd51bfdca025e2ce488696e0a87ceb4c6b')
var db=eth.contract([
]).at('0xb434963fbc8cfed2d622f8d05a792fa612d51f06')
> au
> db
> au.highhestBid()
0
> au.highhestBidder()
"0xbfa12bc27bd8a01a77543a6094b8882f85a2dd58"
> web3.fromWei(eth.getBalance(eth.accounts[1]), "ether")
200.989432884
> web3.fromWei(eth.getBalance(eth.accounts[2]), "ether")
295.985790836
> web3.fromWei(eth.getBalance(eth.accounts[3]), "ether")
199.995910112
> personal.unlockAccount(eth.accounts[1])
Unlock account 0x7e0fc0342326a8645e1913771d42917c3a65c119
Passphrase:
true
> au.bid.sendTransaction({from:eth.accounts[1], gas:500000, value:web3.toWei(1,"ether")})
"0x4875bde8824982f618d7daa5e4d5e29516785acb752c35064c36133670515601"
> web3.fromWei(au.highhestBid(), "ether")
> db.bid.sendTransaction(au.address, {from:eth.accounts[1], gas:50000, value:web3.toWei(5, "ether)})