前言
合约是DAPP的基础,因此在开发DAPP之前,我们需要明白我们的项目是否需要去中心话以及哪些数据需要去中心化,目前的公链还不支持所有的资源都去中心话,这样才能更好的设计我们的项目和合约。接下来将以去中心话的投票系统为例来讲解合约的开发、部署、测试。
合约是什么
合约是一种协议,我们制定好规则后,合约自动执行。
EOSIO智能合约是在区块链上注册并在节点上执行的软件,相关的操作会记录在区块链的存储上。
智能合约定义了接口(操作、参数、数据结构)和实现接口的代码。代码被编译成规范的字节格式,可以在节点上执行。区块链存储会自动存储合约执行的交易记录。
开发合约的语言
我们知道EOSIO的区块链使用WebAssembly(WASM)执行用户生成的应用程序和代码,因此开发高性能和安全智能合约的最佳语言为C++。
相应的C/C++ API参考 https://developers.eos.io/eosio-cpp/reference
合约开发的IDE
这里使用官方推荐Eclipse最为合约的开发工具,具体的配置方法参考: https://developers.eos.io/eosio-cpp/docs/ide-configuration
投票合约开发
在开发之前先了解我们投票系统的设计,本例简约投票系统主要分为以下4个步骤。
- 创建一个主题
- 添加候人
- 对候选人进行投票
- 查看候选人的投票结果
EOSIO的在执行Action的之前会清理一个干净的内存,并且只对当前action有效,一次action执行完成后对象会被释放。因此在action之间传递状态的唯一方法就是把它持久存储到EOSIO的数据库中。
接下来将使用EOSIO的multi_index来存储相应的数据。
创建投票所需要的表
.......
struct [[eosio::table]] polltheme {uint64_t id; //primary keystring themeName; //主题名称uint8_t status = 0; // 0 = closed, 1 = open,2 = finished;uint32_t count = 0; //本次主题的投票总数uint64_t primary_key() const {return id;}
};//候选人
struct [[eosio::table]] candidate {uint64_t id;uint64_t pollthemeId;string candidateName;uint32_t count = 0; //候选人获得的投票次数uint64_t primary_key() const {return id;}
};//投票记录
struct [[eosio::table]] pollrecord {uint64_t id; //primary keyuint64_t pollthemeId;uint64_t candidateId;string user;uint64_t primary_key() const {return id;}};
操作中使用的方法
[[eosio::action]]
void version() {print("vote system version 1.0.0");
}//添加选举主题
[[eosio::action]]
void addtheme(account_name account, string theme) {require_auth(account);eosio_assert(!is_theme_exist(theme), "theme already exists");pollthemes.emplace(_self, [&](auto& p) {p.id = pollthemes.available_primary_key();p.themeName = theme;p.status = 0;p.count = 0;});print("Add new poll theme: ", theme);
}
//添加候选人
[[eosio::action]]
void addcandidate(account_name account, string theme, string name) {require_auth(account);eosio_assert(is_theme_exist(theme),"theme not exists,please add theme first");auto themeid = get_theme_id(theme);eosio_assert(!is_candidate_exist(themeid,name), "candidate is exists");candidates.emplace(_self, [&](auto& c) {c.id = candidates.available_primary_key();c.pollthemeId = themeid;c.candidateName = name;c.count = 0;});print("Add new candidate : ", name);
}[[eosio::action]]
void poll(account_name account, string theme, string name) {require_auth(account);eosio_assert(is_theme_exist(theme),"theme not exists,please add theme first");auto themeid = get_theme_id(theme);auto candidateid = get_candidate_id(themeid,name);eosio_assert(is_candidate_exist(themeid,name), "candidate is not exists");eosio_assert(!is_account_vote(themeid, candidateid, account),"You have already voted");//记录投票信息auto accountNameObj = eosio::name { account };string accountName = accountNameObj.to_string();pollrecords.emplace(_self, [&](auto& r) {r.id = pollrecords.available_primary_key();r.pollthemeId = themeid;r.candidateId = candidateid;r.user = accountName;});//修改候选人的投票信息auto candidate_itr = candidates.find(candidateid);candidates.modify(candidate_itr, _self, [&](auto& p){p.count = p.count + 1;});//修改选举主题的总得票信息auto theme_itr = pollthemes.find(themeid);pollthemes.modify(theme_itr, _self, [&](auto& p){p.count = p.count + 1;});print("vote success");}
创建测试key
cleos create key --to-console
Private key: 5JxsGyZD76BXEoo6psYdqotFtw6Gw62PZsRMHiKvRt3vUYHBT6t
Public key: EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
将private key导入钱包
cleos wallet import
private key: imported private key for: EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
创建部署合约账户
cleos create account eosio myvoter EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
executed transaction: 1f5b02556113a7fa364f82c5df99bb96d2ad0dd728d2d27bf38d00bc5180cbc6 200 bytes 17840 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"myvoter","owner":{"threshold":1,"keys":[{"key":"EOS8SksBtjNEbRNHauHM99aPV...
创建测试用户
这里我们创建3个测试用户
cleos create account eosio voter11 EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
executed transaction: 86c4c93c5d5e4875de5c9403dbba44c4749b323f10f1027e1ca9bdb9c0019df9 200 bytes 203 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"voter11","owner":{"threshold":1,"keys":[{"key":"EOS8SksBtjNEbRNHauHM99aPV...
cleos create account eosio voter12 EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
executed transaction: fdc50ffde7584e65a0c554fabad9938fea3aaff3c5e4dc4aa9f98b88817a18cd 200 bytes 201 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"voter12","owner":{"threshold":1,"keys":[{"key":"EOS8SksBtjNEbRNHauHM99aPV...
cleos create account eosio voter13 EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA EOS8SksBtjNEbRNHauHM99aPVgWgomLzPEpZ9C6Hxf6XccaiouCjA
executed transaction: 558b4e33c24e2ac6f792e53a75ae14ffb0fdf12ad0b1ed1296f88138375787ba 200 bytes 203 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"voter13","owner":{"threshold":1,"keys":[{"key":"EOS8SksBtjNEbRNHauHM99aPV...
生成wasm和abi文件
eosio-cpp -o vote.wasm vote.cpp --abigen
这里使用的eosio-cpp来生成wasm和abi文件,所以对表的申明则需要使用**[[eosio::table]]**,action的申明使用 [[eosio::action]]。
部署合约
cleos set contract myvoter vote
Reading WASM from vote/vote.wasm...
Publishing contract...
executed transaction: ea2447e5686ca8f9425c635a7d5a905487ab5d5f5a3b6cb8fcb06a1f7acce806 7992 bytes 16897 us
# eosio <= eosio::setcode {"account":"myvoter","vmtype":0,"vmversion":0,"code":"0061736d0100000001b9011d60017f0060037f7e7f0060...
# eosio <= eosio::setabi {"account":"myvoter","abi":"0e656f73696f3a3a6162692f312e3000070c61646463616e646964617465000307616363...
测试,version方法,查看系统版本
cleos push action myvoter version '[]' -p myvoter@active
executed transaction: 6b3af1f84bb8e01dab8186dc82f017450fa32fc941b669fc2f11bf3ee502b480 96 bytes 245 us
# myvoter <= myvoter::version ""
>> vote system version 1.0.0
添加投票主题
cleos push action myvoter addtheme '["myvoter","2018最美乡村选举"]' -p myvoter@active
executed transaction: ccfcfe361cdadee0ed1c0c146e1e6bc76f51a05672c3904272e0b06ad0666c37 128 bytes 18289 us
# myvoter <= myvoter::addtheme {"account":"myvoter","theme":"2018最美乡村选举"}
>> Add new poll theme: 2018最美乡村选举
添加候选人
cleos push action myvoter addcandidate '["myvoter","2018最美乡村选举","雪乡"]' -p myvoter@active
executed transaction: 15cf286e1fb449b9d6269eaf6ba5249047c974b6f1adddcc4b4f553b109a45e7 136 bytes 766 us
# myvoter <= myvoter::addcandidate {"account":"myvoter","theme":"2018最美乡村选举","name":"雪乡"}
>> Add new candidate : 雪乡
cleos push action myvoter addcandidate '["myvoter","2018最美乡村选举","图瓦村"]' -p myvoter@active
executed transaction: a2b7b1479c22ce3a1b1b9fca6cb95329184bfefab41639dfe1f3944fdc5d292a 136 bytes 9159 us
# myvoter <= myvoter::addcandidate {"account":"myvoter","theme":"2018最美乡村选举","name":"图瓦村"}
>> Add new candidate : 图瓦村
查看数据库中当前存储的信息
cleos get table myvoter myvoter polltheme
{"rows": [{"id": 0,"themeName": "2018最美乡村选举","status": 0,"count": 0}],"more": false
}cleos get table myvoter myvoter candidate
{"rows": [{"id": 0,"pollthemeId": 0,"candidateName": "雪乡","count": 0},{"id": 1,"pollthemeId": 0,"candidateName": "图瓦村","count": 0}],"more": false
}
对图瓦村投票
cleos push action myvoter poll '["voter11","2018最美乡村选举","图瓦村"]' -p voter11@active
executed transaction: 3635576450efbf01139f5d05c6fecd983fd63c04a128b76648d5562775c52534 136 bytes 1275 us
# myvoter <= myvoter::poll {"account":"voter11","theme":"2018最美乡村选举","name":"图瓦村"}
>> vote success//再次使用同一用户进行投票,可以看到已经投过票的用户不能再继续投票
cleos push action myvoter poll '["voter11","2018最美乡村选举","图瓦村"]' -p voter11@active
Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: You have already voted
pending console output:
对雪乡投票
cleos push action myvoter poll '["voter12","2018最美乡村选举","雪乡"]' -p voter12@active
executed transaction: d715854d923029df9828ea4a283f67c30f227cc33ba904197653d9838a8b17ea 136 bytes 1218 us
# myvoter <= myvoter::poll {"account":"voter12","theme":"2018最美乡村选举","name":"雪乡"}
>> vote success
cleos push action myvoter poll '["voter13","2018最美乡村选举","雪乡"]' -p voter13@active
executed transaction: ee7e91ad2b3e92e3ba843bb44bdd8b0971dafd2a8bd6765df49d5add9be8cc49 136 bytes 1314 us
# myvoter <= myvoter::poll {"account":"voter13","theme":"2018最美乡村选举","name":"雪乡"}
>> vote success
查看投票记录
cleos get table myvoter myvoter pollrecord
{"rows": [{"id": 0,"pollthemeId": 0,"candidateId": 1,"user": "voter11"},{"id": 1,"pollthemeId": 0,"candidateId": 0,"user": "voter12"},{"id": 2,"pollthemeId": 0,"candidateId": 0,"user": "voter13"}],"more": false
}cleos get table myvoter myvoter candidate
{"rows": [{"id": 0,"pollthemeId": 0,"candidateName": "雪乡","count": 2},{"id": 1,"pollthemeId": 0,"candidateName": "图瓦村","count": 1}],"more": false
}cleos get table myvoter myvoter polltheme
{"rows": [{"id": 0,"themeName": "2018最美乡村选举","status": 0,"count": 3}],"more": false
}
总结
该示例介绍了基于本地环境,从合约开发、部署、测试的流程。从大的方面了解开发EOS的合约需要哪些知识。我们的开发完成合约,本地环境测试时第一步,本地环境和主网还是存在一些差别,接下来将讲解如何在麒麟 https://www.cryptokylin.io/ 上进行部署和测试。
完整代码
/** vote.cpp** */#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>using namespace eosio;
using namespace std;class vote: public eosio::contract {
public:vote(account_name self) :contract(self), pollthemes(self, self), candidates(self, self), pollrecords(self, self) {}[[eosio::action]]void version() {print("vote system version 1.0.0");}//添加选举主题[[eosio::action]]void addtheme(account_name account, string theme) {require_auth(account);eosio_assert(!is_theme_exist(theme), "theme already exists");pollthemes.emplace(_self, [&](auto& p) {p.id = pollthemes.available_primary_key();p.themeName = theme;p.status = 0;p.count = 0;});print("Add new poll theme: ", theme);}//添加候选人[[eosio::action]]void addcandidate(account_name account, string theme, string name) {eosio_assert(is_theme_exist(theme),"theme not exists,please add theme first");auto themeid = get_theme_id(theme);eosio_assert(!is_candidate_exist(themeid,name), "candidate is exists");candidates.emplace(_self, [&](auto& c) {c.id = candidates.available_primary_key();c.pollthemeId = themeid;c.candidateName = name;c.count = 0;});print("Add new candidate : ", name);}[[eosio::action]]void poll(account_name account, string theme, string name) {eosio_assert(is_theme_exist(theme),"theme not exists,please add theme first");auto themeid = get_theme_id(theme);auto candidateid = get_candidate_id(themeid,name);eosio_assert(is_candidate_exist(themeid,name), "candidate is not exists");eosio_assert(!is_account_vote(themeid, candidateid, account),"You have already voted");//记录投票信息auto accountNameObj = eosio::name { account };string accountName = accountNameObj.to_string();pollrecords.emplace(_self, [&](auto& r) {r.id = pollrecords.available_primary_key();r.pollthemeId = themeid;r.candidateId = candidateid;r.user = accountName;});//修改候选人的投票信息auto candidate_itr = candidates.find(candidateid);candidates.modify(candidate_itr, _self, [&](auto& p){p.count = p.count + 1;});//修改选举主题的总得票信息auto theme_itr = pollthemes.find(themeid);pollthemes.modify(theme_itr, _self, [&](auto& p){p.count = p.count + 1;});print("vote success");}private://选举的主题struct [[eosio::table]] polltheme {uint64_t id; //primary keystring themeName; //主题名称uint8_t status = 0; // 0 = closed, 1 = open,2 = finished;uint32_t count = 0; //本次主题的投票总数uint64_t primary_key() const {return id;}};//候选人struct [[eosio::table]] candidate {uint64_t id;uint64_t pollthemeId;string candidateName;uint32_t count = 0; //候选人获得的投票次数uint64_t primary_key() const {return id;}};//投票记录struct [[eosio::table]] pollrecord {uint64_t id; //primary keyuint64_t pollthemeId;uint64_t candidateId;string user;uint64_t primary_key() const {return id;}};typedef eosio::multi_index<N(polltheme), polltheme> polltheme_index;typedef eosio::multi_index<N(candidate), candidate> candidate_index;typedef eosio::multi_index<N(pollrecord), pollrecord> pollrecord_index;polltheme_index pollthemes;candidate_index candidates;pollrecord_index pollrecords;/*** true 存在*/bool is_theme_exist(string theme) {for(auto &item : pollthemes){if(item.themeName == theme) {return true;}}return false;}/*** 查找*/uint64_t get_theme_id(string theme){for(auto &item : pollthemes){if(item.themeName == theme){return item.id;}}return 0;}/*** true 存在*/bool is_candidate_exist(uint64_t themeId,string name) {for(auto &item : candidates){if(item.pollthemeId == themeId && item.candidateName == name){return true;}}return false;}uint64_t get_candidate_id(uint64_t themeId,string name){for (auto &item : candidates) {if (item.pollthemeId == themeId && item.candidateName == name) {return item.id;}}return 0;}/*** ture 表示已经对本次主题投过票*/bool is_account_vote(uint64_t themeid, uint64_t candidateid, account_name user) {auto accountNameObj = eosio::name { user };string accountName = accountNameObj.to_string();for (auto& vote : pollrecords) {if (vote.candidateId == candidateid && vote.pollthemeId == themeid&& vote.user == accountName) {return true;}}return false;}};EOSIO_ABI( vote, (version)(addtheme)(addcandidate) (poll))