區塊鏈100講:今天想要教大家發個幣
本文作者:HiBlock區塊鏈技術佈道群-胡鍵
原文發佈於簡書
https://www.jianshu.com/p/d78353772029
本講將通過一些簡單的例子從近處看以太坊DApp的開發細節。偷偷告訴你,本文會涉及到以太坊中的一個熱門場景:「發幣」,滿足一下各位苦逼的開發當一回大佬的願望
1
背景
本文用到的開發工具:
Node
Truffle
相關的包:
yargs,cli庫
web3,json-rpc抽象
truffle-contract,合約抽象
openzeppelin-solidity,安全合約庫
文章中創建的項目為一個「node + truffle」工程,對外提供cli。這個cli暴露了兩條命令:
$./app.js help
app.js [命令]
命令:
app.js simple-data [from] access simple-data contract from an
[value] external address.
app.js myico [purchaser] commands about my ico.
[value]
選項:
--version 顯示版本號 [布爾]
--help 顯示幫助信息 [布爾]
選擇以cli而非gui的方式作為dapp的前端主要的理由:
當前的重點是迅速掌握以太坊dapp開發的套路。
cli相比起gui來講,省去了很多麻煩事。而且,我對於gui的開發,實在興趣不大。
2
準備
那麼,讓我們先來準備工程的架子:
mkdir 目錄 && cd 目錄
npm init
truffle init
npm install yargs --save
執行完成後,cli工程需要基本環境就都具備了。之後,在項目的工程根下創建app.js,它將作為整個工程的入口。並且工程採用Command Module(https://github.com/yargs/yargs/blob/master/docs/advanced.md#providing-a-command-module)的方式組織。
app.js的內容如下:
#!/usr/bin/env node
require("yargs")
.command(require("./simple-data.js"))
.command(require("./myico.js"))
.help()
.argv
其中的兩條命令以module方式組織,分別對應:
simple-data,簡單合約交互
my i-c-o,i-c-o合約交互
3
simple-data
simple-data命令展示了一個簡單的前端和合約交互的例子,為編寫複雜交互提供了參考。它的整個過程如下:
(1)npm install web3 --save
(2)npm install truffle-contract --save
(3)編寫合約
pragma solidity ^0.4.23;
contract SimpleData {
address public owner;
uint data;
constructor() public {
owner = msg.sender;
}
function set(uint x) public{
data = x;
}
function get() view public returns (uint) {
return data;
}
}
(4)編寫Migration文件
const SimpleData = artifacts.require("./SimpleData.sol");
module.exports = function(deployer) {
deployer.deploy(SimpleData);
};
(5)編寫Command
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"));
const contract = require("truffle-contract");
const SimpleDataContract = require("./build/contracts/SimpleData.json");
const simpleData = contract(SimpleDataContract);
simpleData.setProvider(web3.currentProvider);
if (typeof simpleData.currentProvider.sendAsync !== "function") {
simpleData.currentProvider.sendAsync = function() {
return simpleData.currentProvider.send.apply(
simpleData.currentProvider, arguments
);
};
}
exports.command = "simple-data [from] [value]";
exports.describe = "access simple-data contract from an
external address.";exports.handler = function(argv) {
if(argv.action == "get") {
simpleData.deployed().then(function(instance){
instance.get().then(function(result){
console.log(+result);
})
});
} else if(argv.action == "set") {
if(!argv.value) {
console.log("No value provided!");
return;
}
simpleData.deployed().then(function(instance){
instance.set(argv.value, ).then(function(result){
console.log(result);
})
});
} else {
console.log("Unknown action!");
}
}
說明:
「http://localhost:9545」對應「truffle develop」環境中的埠。
「./build/contracts/SimpleData.json」由「truffle compile」產生,這個json文件將和truffle-contract一起配合產生針對於合約的抽象。
「if(typeof ... !== "function") { ... }」這個if block是對於truffle-contract這個issue的walkaround。
隨後的exports則是yargs command module的介面要求。對於命令:「simple-data [from] [value]」,其格式由yargs解析:,必填;[...],選填
編譯部署之後,就可以簡單的試用了(先給app.js可執行許可權):
app.js simple-data get
app.js simple-data set 地址 值
4
my--i-c-o
接下來,就到了最讓人期待的時刻:發幣,更準確的說是基於ERC 20的代幣發放。因為以太坊中各個幣種有不同的含義和用途,比如另一個常見的幣種:ERC 721,它發行的每個token都是獨一無二不可互換的,比如以太貓。
關於發幣,究其本質就是實現特定的合約介面。從開發的投入產出比來講,我建議採用OpenZeppelin:
跟錢打交道的事情需要慎重,由不安全合約導致的問題屢見不鮮。
自己編寫安全合約並不簡單。
OpenZeppelin包含了當前安全合約的最簡實踐,其背後的公司本身就提供安全審計服務。
可復用合約代碼加快了合約的開發。
以下是使用OpenZeppelin開發i-c-o的過程:
(1)npm install -E openzeppelin-solidity
(2)i-c-o的過程由兩個合約組成,它們都將直接基於OpenZeppelin的合約完成。
coin,代幣
crowdsale,眾籌
(3)代幣合約
pragma solidity ^0.4.23;
import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol";
contract MyCoin is MintableToken {
string public name = "MY COIN"; // 代幣名稱
string public symbol = "MYC"; // 代幣代碼
uint8 public decimal = 18; // 位數
}
(4)眾籌合約
pragma solidity ^0.4.23;
import "../node_modules/openzeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol";
import "../node_modules/openzeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol";
contract MyCrowdsale is TimedCrowdsale, MintedCrowdsale {
constructor (
uint256 _openingTime,
uint256 _closingTime,
uint256 _rate,
address _wallet,
MintableToken _token
)
public
Crowdsale(_rate, _wallet, _token)
TimedCrowdsale(_openingTime, _closingTime) {
}
}
幾行代碼就完成了核心合約的開發,這要歸功於咱們選的框架,;)
(5)Migration腳本
const MyCoin = artifacts.require("./MyCoin.sol");
const MyCrowdsale = artifacts.require("./MyCrowdsale.sol");
module.exports = function(deployer, network, accounts) {
const openingTime = web3.eth.getBlock("latest").timestamp + 2;
const closingTime = openingTime + 3600;
const rate = new web3.BigNumber(1000);
const wallet = accounts[1];
deployer.deploy(MyCoin).then(function() {
return deployer.deploy(MyCrowdsale, openingTime, closingTime, rate, wallet, MyCoin.address);
}).then(function() {
return MyCoin.deployed();
}).then(function(instance) {
var coin = instance;
coin.transferOwnership(MyCrowdsale.address);
});
};
說明:
上面的合約定義很清楚地表明,眾籌需要有Token的地址,因此眾籌合約需要在Token部署成功之後進行。
眾籌合約需要得到Token的所有權才能進行發行。一開始,Token的owner是部署者(這裡是account[0]),因此在眾籌合約部署完成之後需要完成Token所有權的移交。
最後一步非常關鍵,否則會出現類似下面的錯誤:
Error: VM Exception while processing transaction: revert
at Object.InvalidResponse ...
...
(6)最後,就是my i-c-o的命令編寫了。
const Web3 = require("web3");
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545"));
const contract = require("truffle-contract");
const MyCoin = require("./build/contracts/MyCoin.json");
const myCoin = contract(MyCoin);
myCoin.setProvider(web3.currentProvider);
if (typeof myCoin.currentProvider.sendAsync !== "function") {
myCoin.currentProvider.sendAsync = function() {
return myCoin.currentProvider.send.apply(
myCoin.currentProvider, arguments
);
};
}
const MyCrowdsale = require("./build/contracts/MyCrowdsale.json");
const myCrowdsale = contract(MyCrowdsale);
myCrowdsale.setProvider(web3.currentProvider);
if (typeof myCrowdsale.currentProvider.sendAsync !== "function") {
myCrowdsale.currentProvider.sendAsync = function() {
return myCrowdsale.currentProvider.send.apply(
myCrowdsale.currentProvider, arguments
);
};
}
exports.command = "myico [purchaser] [value]";
exports.describe = "commands about my ico.";
exports.handler = function(argv) {
if(argv.command == "hasClosed") {
myCrowdsale.deployed().then(function(instance){
instance.hasClosed().then(function(result){
console.log(result);
});
});
} else if(argv.command == "deliver") {
myCrowdsale.deployed().then(function(instance){
instance.token().then(function(address){
instance.sendTransaction()
.then(function(result){
console.log("done!");
}).catch(function(error){
console.error(error);
});
});
});
} else if(argv.command == "totalSupply") {
myCrowdsale.deployed().then(function(instance){
instance.token().then(function(address){
let coinInstance = myCoin.at(address);
coinInstance.totalSupply().then(function(result){
console.log(+result);
});
});
});
} else if(argv.command == "balance") {
myCrowdsale.deployed().then(function(instance){
instance.token().then(function(address){
let coinInstance = myCoin.at(address);
coinInstance.balanceOf(argv.purchaser).then(function(balance){
console.log(+balance);
});
});
});
} else {
console.log("Unknown command!");
}
}
有了前面simple-data命令代碼的說明,這裡的代碼應該不會有任何難點了。其中的子命令:
hasClosed,眾籌是否結束
totalSupply,眾籌中產生的token總量
balance,某個賬戶的代幣數
deliver,給賬戶發行代幣
到這裡,相信大家應該不會再覺得「發幣」有什麼神秘的了,接下來如何將其應用到業務中,作為業務的催化劑(而不是割韭菜利器)就靠各位的想像力了。(註:本文只分享技術,不做任何項目及投資建議)
-- 線上課程推薦--
TAG:區塊鏈社區HiBlock |