在前一篇文章中, 我們分享了如何搭建dapp開發(fā)環(huán)境, 這篇文章我們分享如何
創(chuàng)建卡牌游戲合約, 如何把合約部署到區(qū)塊鏈模擬器.
用truffle 創(chuàng)建工程
打開終端, 創(chuàng)建一個(gè)空目錄作為本次項(xiàng)目的根目錄, 然后輸入
truffle unbox react
truffle 會(huì)開始下載相關(guān)的文件, 請耐心等待一會(huì)兒.
找到truffle.js, 填入如下內(nèi)容, 是為truffle指定要去鏈接的區(qū)塊鏈網(wǎng)絡(luò)
module.exports = {
networks: {
dev: {
host: "127.0.0.1",
port: 7545,
network_id: "*"
}
}
};
編寫一個(gè)智能合約
目前以太網(wǎng)絡(luò)上已經(jīng)有比較多的智能合約了, 我們可以找別人的智能合約來參考.
安裝Metamask
Metamask是一款瀏覽器插件, 可以讓我們從瀏覽器中訪問到以太坊區(qū)塊鏈的數(shù)據(jù). 因?yàn)镚oogle插件商店需要愛國上網(wǎng)才能訪問, 所以這里推薦使用Firefox, 并安裝Metamask插件.
找合約模板
訪問一個(gè)已經(jīng)上線的dapp的網(wǎng)站, 比如cryptobeauty.cc, 走一遍購買流程, 在最后就能拿到合約的地址, 然后上etherscan.io就可以看到合約的源碼.
這里我們拿到的cryptobeauty的合約源碼如下:
pragma solidity ^0.4.19;
contract AccessControl {
address public owner;
address[] public admins;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
modifier onlyAdmins {
bool found = false;
for (uint i = 0; i < admins.length; i++) {
if (admins[i] == msg.sender) {
found = true;
break;
}
}
require(found);
_;
}
function addAdmin(address _adminAddress) public onlyOwner {
admins.push(_adminAddress);
}
function transferOwnership(address newOwner) public onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
contract ERC721 {
// Required Functions
function implementsERC721() public pure returns (bool);
function totalSupply() public view returns (uint256);
function balanceOf(address _owner) public view returns (uint256);
function ownerOf(uint256 _tokenId) public view returns (address);
function transfer(address _to, uint _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function transferFrom(address _from, address _to, uint256 _tokenId) public;
// Optional Functions
function name() public pure returns (string);
function symbol() public pure returns (string);
// Required Events
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
}
contract CryptoBeauty is AccessControl, ERC721 {
// Event fired for every new beauty created
event Creation(uint256 tokenId, string name, address owner);
// Event fired whenever beauty is sold
event Purchase(uint256 tokenId, uint256 oldPrice, uint256 newPrice, address prevOwner, address owner, uint256 charityId);
// Event fired when price of beauty changes
event PriceChange(uint256 tokenId, uint256 price);
// Event fired when charities are modified
event Charity(uint256 charityId, address charity);
string public constant NAME = "Crypto Beauty";
string public constant SYMBOL = "BEAUTY";
// Initial price of card
uint256 private startingPrice = 0.005 ether;
uint256 private increaseLimit1 = 0.5 ether;
uint256 private increaseLimit2 = 50.0 ether;
uint256 private increaseLimit3 = 100.0 ether;
// Charities enabled in the future
bool charityEnabled;
// Beauty card
struct Beauty {
// unique name of beauty
string name;
// selling price
uint256 price;
// maximum price
uint256 maxPrice;
}
Beauty[] public beauties;
address[] public charities;
mapping (uint256 => address) public beautyToOwner;
mapping (address => uint256) public beautyOwnershipCount;
mapping (uint256 => address) public beautyToApproved;
function CryptoBeauty() public {
owner = msg.sender;
admins.push(msg.sender);
charityEnabled = false;
}
function implementsERC721() public pure returns (bool) {
return true;
}
function totalSupply() public view returns (uint256) {
return beauties.length;
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return beautyOwnershipCount[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address owner) {
owner = beautyToOwner[_tokenId];
require(owner != address(0));
}
function transfer(address _to, uint256 _tokenId) public {
require(_to != address(0));
require(beautyToOwner[_tokenId] == msg.sender);
_transfer(msg.sender, _to, _tokenId);
}
function approve(address _to, uint256 _tokenId) public {
require(beautyToOwner[_tokenId] == msg.sender);
beautyToApproved[_tokenId] = _to;
Approval(msg.sender, _to, _tokenId);
}
function transferFrom(address _from, address _to, uint256 _tokenId) public {
require(beautyToApproved[_tokenId] == _to);
require(_to != address(0));
require(beautyToOwner[_tokenId] == _from);
_transfer(_from, _to, _tokenId);
}
function name() public pure returns (string) {
return NAME;
}
function symbol() public pure returns (string) {
return SYMBOL;
}
function addCharity(address _charity) public onlyAdmins {
require(_charity != address(0));
uint256 newCharityId = charities.push(_charity) - 1;
// emit charity event
Charity(newCharityId, _charity);
}
function deleteCharity(uint256 _charityId) public onlyAdmins {
delete charities[_charityId];
// emit charity event
Charity(_charityId, address(0));
}
function getCharity(uint256 _charityId) public view returns (address) {
return charities[_charityId];
}
function createBeauty(string _name, address _owner, uint256 _price) public onlyAdmins {
if (_price <= 0.005 ether) {
_price = startingPrice;
}
Beauty memory _beauty = Beauty({
name: _name,
price: _price,
maxPrice: _price
});
uint256 newBeautyId = beauties.push(_beauty) - 1;
Creation(newBeautyId, _name, _owner);
_transfer(address(0), _owner, newBeautyId);
}
function newBeauty(string _name, uint256 _price) public onlyAdmins {
createBeauty(_name, msg.sender, _price);
}
function getBeauty(uint256 _tokenId) public view returns (
string beautyName,
uint256 sellingPrice,
uint256 maxPrice,
address owner
) {
Beauty storage beauty = beauties[_tokenId];
beautyName = beauty.name;
sellingPrice = beauty.price;
maxPrice = beauty.maxPrice;
owner = beautyToOwner[_tokenId];
}
function purchase(uint256 _tokenId, uint256 _charityId) public payable {
// seller
address oldOwner = beautyToOwner[_tokenId];
// current price
uint sellingPrice = beauties[_tokenId].price;
// buyer
address newOwner = msg.sender;
require(oldOwner != newOwner);
require(newOwner != address(0));
require(msg.value >= sellingPrice);
uint256 devCut;
uint256 nextPrice;
if (sellingPrice < increaseLimit1) {
devCut = SafeMath.div(SafeMath.mul(sellingPrice, 5), 100); // 5%
nextPrice = SafeMath.div(SafeMath.mul(sellingPrice, 200), 95);
} else if (sellingPrice < increaseLimit2) {
devCut = SafeMath.div(SafeMath.mul(sellingPrice, 4), 100); // 4%
nextPrice = SafeMath.div(SafeMath.mul(sellingPrice, 135), 96);
} else if (sellingPrice < increaseLimit3) {
devCut = SafeMath.div(SafeMath.mul(sellingPrice, 3), 100); // 3%
nextPrice = SafeMath.div(SafeMath.mul(sellingPrice, 125), 97);
} else {
devCut = SafeMath.div(SafeMath.mul(sellingPrice, 2), 100); // 2%
nextPrice = SafeMath.div(SafeMath.mul(sellingPrice, 115), 98);
}
uint256 excess = SafeMath.sub(msg.value, sellingPrice);
if (charityEnabled == true) {
// address of choosen charity
address charity = charities[_charityId];
// check if charity address is not null
require(charity != address(0));
// 1% of selling price
uint256 donate = uint256(SafeMath.div(SafeMath.mul(sellingPrice, 1), 100));
// transfer money to charity
charity.transfer(donate);
}
// set new price
beauties[_tokenId].price = nextPrice;
// set maximum price
beauties[_tokenId].maxPrice = nextPrice;
// transfer card to buyer
_transfer(oldOwner, newOwner, _tokenId);
// transfer money to seller
if (oldOwner != address(this)) {
oldOwner.transfer(SafeMath.sub(sellingPrice, devCut));
}
// emit event that beauty was sold;
Purchase(_tokenId, sellingPrice, beauties[_tokenId].price, oldOwner, newOwner, _charityId);
// transfer excess back to buyer
if (excess > 0) {
newOwner.transfer(excess);
}
}
// owner can change price
function changePrice(uint256 _tokenId, uint256 _price) public {
// only owner can change price
require(beautyToOwner[_tokenId] == msg.sender);
// price cannot be higher than maximum price
require(beauties[_tokenId].maxPrice >= _price);
// set new price
beauties[_tokenId].price = _price;
// emit event
PriceChange(_tokenId, _price);
}
function priceOfBeauty(uint256 _tokenId) public view returns (uint256) {
return beauties[_tokenId].price;
}
function tokensOfOwner(address _owner) public view returns(uint256[]) {
uint256 tokenCount = balanceOf(_owner);
uint256[] memory result = new uint256[](tokenCount);
uint256 total = totalSupply();
uint256 resultIndex = 0;
for(uint256 i = 0; i <= total; i++) {
if (beautyToOwner[i] == _owner) {
result[resultIndex] = i;
resultIndex++;
}
}
return result;
}
function _transfer(address _from, address _to, uint256 _tokenId) private {
beautyOwnershipCount[_to]++;
beautyToOwner[_tokenId] = _to;
if (_from != address(0)) {
beautyOwnershipCount[_from]--;
delete beautyToApproved[_tokenId];
}
Transfer(_from, _to, _tokenId);
}
function enableCharity() external onlyOwner {
require(!charityEnabled);
charityEnabled = true;
}
function disableCharity() external onlyOwner {
require(charityEnabled);
charityEnabled = false;
}
function withdrawAll() external onlyAdmins {
msg.sender.transfer(this.balance);
}
function withdrawAmount(uint256 _amount) external onlyAdmins {
msg.sender.transfer(_amount);
}
}
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
在工程中創(chuàng)建智能合約文件
進(jìn)入工程目錄下面的contracts目錄, 會(huì)看到里面已經(jīng)有Migrations.sol和SimpleStorage.sol兩個(gè)文件, 我們創(chuàng)建CryptoBeauty.sol, 并填入上面的合約內(nèi)容.
打開區(qū)塊鏈模擬器
找到我們下載的ganache.AppImage文件, 雙擊運(yùn)行或者在命令行輸入./ganache-*.AppImage 來運(yùn)行g(shù)anache.
ganache打開之后我們可以看到左上角的current block 顯示為0, 同時(shí)ganache創(chuàng)建的每個(gè)賬戶都有100ETH的資金.
部署合約
進(jìn)入migrations文件夾, 創(chuàng)建3_deploy_beauty.js并在里面輸入下面的內(nèi)容
var CryptoBeauty = artifacts.require("./CryptoBeauty.sol");
module.exports = function(deployer) {
deployer.deploy(CryptoBeauty);
};
保存文件并退出
然后在工程的根目錄下面輸入
truffle compile && truffle migrate --network dev
看到Saving successful miration to network...的消息就表示已經(jīng)部署成功了. 我們也可以調(diào)出ganache, 會(huì)看到這個(gè)時(shí)候current block已經(jīng)不在是0, 同時(shí)第一個(gè)賬戶里面的資金表少了, 因?yàn)椴渴鸷霞s的過程支付了gas.
創(chuàng)建角色
CryptoBeauty里面的卡牌是怎么來的呢, 答案就是: 通過與智能合約交互創(chuàng)建的. 我們下面就來創(chuàng)建角色.
首先我們回過頭去看下智能合約, 里面有一個(gè)叫newBeauty的函數(shù)用來創(chuàng)建角色, 還有一個(gè) totalSupply()函數(shù)返回合約總共有多少個(gè)角色.
function newBeauty(string _name, uint256 _price) public onlyAdmins {
createBeauty(_name, msg.sender, _price);
}
function totalSupply() public view returns (uint256) {
return beauties.length;
}
newBeauty函數(shù)有兩個(gè)參數(shù), 一個(gè)是字符串型的角色的名字, 另一個(gè)是角色的價(jià)格, 注意這里的單位是wei.
如何創(chuàng)建角色呢?.
在工程的根目錄下輸入
truffle console --network dev
然后在打開的truffle console中輸入
CryptoBeauty.deployed().then(function(instance) {instance.newBeauty('FBB', 10000)})
CryptoBeauty.deployed().then(function(instance) {instance.newBeauty('LBB', 10000)})
CryptoBeauty.deployed().then(function(instance) {instance.newBeauty('LYF', 10000)})
這就完成了創(chuàng)建角色, 怎么驗(yàn)證是不是成功了呢?, 我們可以通過下面的命令調(diào)用合約的totalSupply()方法
CryptoBeauty.deployed().then(function(instance) {instance.totalSupply.call().then(function(tot){console.log('total supplay is ' + tot)})})
這里輸出的數(shù)字和上面我們創(chuàng)建的角色的數(shù)量一致就表明角色已經(jīng)創(chuàng)建成功了.
好了, 本期的課程就到這里, 如果需要視頻課程, 請掃描下面的二維碼加入課程群.
更多內(nèi)容請關(guān)注:星號區(qū)塊鏈