我必須認可,學習eosio一直沒有閒庭信步的感受,我能夠看到爲何不少人說它有一個陡峭的學習曲線。隨着eosio軟件繼續經歷大量快速發展,文檔數量有限,不多有工做實例可供參考。我已經被困了好幾回,也但願幫助改善下一個開發人員的體驗。在本文中,我將經過將其分解爲單獨的部分來討論eosio.token
合約。php
eosio.token合約容許建立許多不一樣的代幣。這使任何人都可以建立和發送代幣。每一個代幣必須由issuer
賬戶發行。因爲賬戶能夠包含多方,所以你可使用具備全部者和活動權限的普通賬戶或自定義配置賬戶來建立和管理代幣。每一個代幣都是asset
類型,以下所示:java
1000000000.0000 SYS 1.0000 SYMBOL 0.10 SYS
asset
類型是一個數字(若是我沒記錯的話能夠達到18位小數)和一個能夠在1-7個大寫字母之間的符號。此合約有三個操做可用於與之交互。它們是:建立,發佈和轉帳。node
建立用於定義新代幣的特徵。這包括代幣asset
符號,最大供應量以及容許發出代幣的賬戶。建立還會將新代幣配置保留在區塊鏈上。這意味着新代幣配置的存儲必須由某人放置。正如你稍後將看到的,部署此合約的賬戶(在咱們的案例中爲'eosio.token')也將支付代幣配置存儲。python
發佈用於增長代幣的有效供應。能夠持續發出代幣,直到達到最大供應量。在代幣建立期間定義的issuer
必須批准此操做才能使其成功。android
轉帳讓一個賬戶將代幣轉移到另外一個賬戶。ios
你應該知道的第一件事是每一個eosio智能合約都屬於一個eosio賬戶。合約基本上是其餘賬戶能夠與之交互的對象。合約包含在區塊鏈上執行代碼的操做actions
。合約能夠直接訪問區塊鏈上的存儲,刪除和更新數據。程序員
將一個action推送到合約須要至少一個賬戶的受權。根據合約的複雜性,可能須要進一步的賬戶和權限。賬戶能夠由基於權限的配置中設置的單個或多個我的組成。智能合約只能由一個賬戶運行,而一個賬戶只能擁有一個智能合約。最佳作法是爲賬戶和合約提供相同(小寫)的名稱。web
在你與eosio.token合約進行交互以前,你須要建立一個具備相同名稱的賬戶,並將合約部署到該賬戶。mongodb
首先建立一個賬戶編程
$cleos create account eosio eosio.token <OWNER-KEY> <ACTIVE-KEY>
而後編譯合約
$cd eos/contract/eosio.token $eosiocpp -o eosio.token.wast eosio.token.cpp
最後將合約部署到賬戶上
$cleos set contract eosio.token ../eosio.token
你能夠驗證合約是否已部署
$cleos get code eosio.token
合約分爲兩個文件eosio.token.cpp
和eosio.token.hpp
。.hpp
文件定義合約類,操做和表,而.cpp
文件實現操做邏輯。讓咱們首先看一下將用於實例化合約對象的合約類。(我從eosio.token.hpp
中刪除了一些遺留代碼)
/** * @file * @copyright defined in eos/LICENSE.txt */ #pragma once #include <eosiolib/asset.hpp> #include <eosiolib/eosio.hpp> #include <string> namespace eosiosystem { class system_contract; } namespace eosio { using std::string; class token : public contract { public: token( account_name self ):contract(self){} void create( account_name issuer, asset maximum_supply); void issue( account_name to, asset quantity, string memo ); void transfer( account_name from, account_name to, asset quantity, string memo ); private: friend eosiosystem::system_contract; inline asset get_supply( symbol_name sym )const; inline asset get_balance( account_name owner, symbol_name sym )const; struct account { asset balance; uint64_t primary_key()const { return balance.symbol.name(); } }; struct currency_stats { asset supply; asset max_supply; account_name issuer; uint64_t primary_key()const { return supply.symbol.name(); } }; typedef eosio::multi_index<N(accounts), account> accounts; typedef eosio::multi_index<N(stat), currency_stats> stats; void sub_balance( account_name owner, asset value ); void add_balance( account_name owner, asset value, account_name ram_payer ); }; asset token::get_supply( symbol_name sym )const { stats statstable( _self, sym ); const auto& st = statstable.get( sym ); return st.supply; } asset token::get_balance( account_name owner, symbol_name sym )const { accounts accountstable( _self, owner ); const auto& ac = accountstable.get( sym ); return ac.balance; } } /// namespace eosio
構造函數和操做被定義爲公共成員函數。構造函數採用賬戶名稱(將是部署合約的賬戶,也就是eosio.token)並將其設置爲contract變量。請注意,此類繼承自eosio::contract
。
表和helper函數做爲私有成員提供。兩個內聯函數在底部定義但從未使用過。這給咱們留下了重要的函數sub_balance()
和add_balance()
。這些將由轉移操做調用。
定義的兩個表是accounts
和stat
。accounts
表由不一樣的account
對象組成,每一個account
對象持有不一樣代幣的餘額。stat
表由持有供應,max_supply
和發行者的currency_stats
對象(由struct currency_stats
定義)組成。在繼續以前,重要的是要知道該合約將數據保存到兩個不一樣的範圍。accounts
表的範圍限定爲eosio賬戶,stat
表的範圍限定爲代幣符號名稱。
根據eosio::multi_index
定義,code
是具備寫權限的賬戶的名稱,scope
是存儲數據的賬戶。
範圍本質上是一種在合約中劃分數據的方法,以便只能在定義的空間內訪問。在代幣合約中,每一個eosio賬戶都用做accounts
表的範圍。accounts
表是一個包含多個account
對象的多索引容器。每一個account
對象都由其代幣符號編制索引,幷包含代幣餘額。使用其範圍查詢用戶的accounts
表時,將返回用戶具備現有餘額的全部代幣的列表。
這是我如何想象它。
在上圖中,有一個名爲tom的eosio賬戶,他有本身的範圍。在他的範圍內是一個名爲accounts
的表。在該表中是一個單獨的account
對象,用於他持有的每一個代幣,SYS
和EOS
。
還有一個名爲stat
的第二個表。此表將包含現有代幣的狀態。新標記在其本身的符號名稱範圍內建立。範圍內是一個包含currency_stats
對象的stat
表。與包含許多不一樣account
對象的accounts
表不一樣,stat
表僅包含給定標記符號的單個currency_stats
對象。
操做在.cpp
文件中實現。要建立新代幣,必須發送建立操做。Create
有兩個參數:發行者,以及新代幣的最大供應量。issuer
是惟一容許增長新代幣供應的人。issuer
不能超過最高供應量發行。
第一行代碼只須要合約賬戶自己的受權。這能夠在推進操做時使用命令行標誌-p eosio.token
給出。
接下來的幾行提取傳入的maximum_supply
資產的符號並執行一些錯誤處理。若是任何eosio_assert
失敗,那麼全部代碼都將被回滾,而且交易不會被推送到區塊鏈。
一個stat
表使用符號名稱(標記符號)做爲其範圍構造爲statstable
。代碼檢查代幣是否已存在。若是沒有,則建立新代幣狀態並將其保存到區塊鏈中。emplace
函數中的第一個參數_self
意味着此合約賬戶eosio.token
將支付投注存儲。
請注意,保存supply
的符號,由於它用做定位錶行的鍵,但供應量還沒有發出。
你如今能夠執行下一個操做,發佈。發佈將採用將收到已發行代幣的賬戶,發出的代幣數量和備忘錄。發佈操做在一箇中執行兩個操做,由於它將修改建立的代幣供應並調用轉帳操做以發送已發佈的代幣。一樣,前幾行提取代幣符號並執行錯誤檢查。
如下代碼部分將使用符號名稱做爲範圍構造stat
表。這用做查找先前使用create action
建立的代幣的鍵。
請注意,從statstable.find()
返回的existing currency_stat
是一個指向找到的對象的迭代器。爲簡潔起見,聲明瞭一個名爲st
的新變量,並將其設置爲existing
迭代器指向的實際對象。這讓咱們可使用.
運算符訪問成員變量而不是指針表示法->
。
建立代幣的issuer
者須要簽署此交易,並執行更多錯誤處理。
最後,修改現有代幣的currency_stats st
,並將已發放的quantity
添加到supply
。issuer
也將此supply
添加到他們的餘額中,以便初始供應能夠追溯到他們的賬戶。
緊接着,經過SEND_INLINE_ACTION()
調用transfer
函數,這會將資金進行轉移。論點以下:
*this
是行動所屬的合約代碼。transfer
操做的名稱。{st.issuer, N(active)}
操做所需的權限。{st.issuer, to, quantity, memo}
操做自己的參數。這將咱們帶到最後的轉帳操做。轉帳操做將從from
,to
,quantity
和memo
獲取四個輸入參數。from
是誰將發送代幣,所以quantity
將從他們的餘額中減去。to
是誰將收到代幣,所以quantity
將被添加到他們的餘額。quantity
是要發送的代幣數量,memo
是一個能夠與交易一塊兒發送的字符串。memo
未在本合約中使用或存儲。
該操做首先要求from
accounts賬戶權限並對from
和to
賬戶執行發佈處理。該符號從quantity
提取並用於獲取代幣的currency_stats
。
require_recipient()
函數將在操做完成時通知發送方和接收方。
完成更多發佈處理,最後調用兩個私有函數sub_balance()
和add_balance()
以從發送add_balance()
減去代幣餘額並增長接收方的代幣餘額。
這是完整的'eosio.token.cpp'文件。
/** * @file * @copyright defined in eos/LICENSE.txt */ #include "eosio.token.hpp" namespace eosio { void token::create( account_name issuer, asset maximum_supply ) { require_auth( _self ); auto sym = maximum_supply.symbol; eosio_assert( sym.is_valid(), "invalid symbol name" ); eosio_assert( maximum_supply.is_valid(), "invalid supply"); eosio_assert( maximum_supply.amount > 0, "max-supply must be positive"); stats statstable( _self, sym.name() ); auto existing = statstable.find( sym.name() ); eosio_assert( existing == statstable.end(), "token with symbol already exists" ); statstable.emplace( _self, [&]( auto& s ) { s.supply.symbol = maximum_supply.symbol; s.max_supply = maximum_supply; s.issuer = issuer; }); } void token::issue( account_name to, asset quantity, string memo ) { auto sym = quantity.symbol; eosio_assert( sym.is_valid(), "invalid symbol name" ); eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" ); auto sym_name = sym.name(); stats statstable( _self, sym_name ); auto existing = statstable.find( sym_name ); eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" ); const auto& st = *existing; require_auth( st.issuer ); eosio_assert( quantity.is_valid(), "invalid quantity" ); eosio_assert( quantity.amount > 0, "must issue positive quantity" ); eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); eosio_assert( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply"); statstable.modify( st, 0, [&]( auto& s ) { s.supply += quantity; }); add_balance( st.issuer, quantity, st.issuer ); if( to != st.issuer ) { SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} ); } } void token::transfer( account_name from, account_name to, asset quantity, string memo ) { eosio_assert( from != to, "cannot transfer to self" ); require_auth( from ); eosio_assert( is_account( to ), "to account does not exist"); auto sym = quantity.symbol.name(); stats statstable( _self, sym ); const auto& st = statstable.get( sym ); require_recipient( from ); require_recipient( to ); eosio_assert( quantity.is_valid(), "invalid quantity" ); eosio_assert( quantity.amount > 0, "must transfer positive quantity" ); eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" ); sub_balance( from, quantity ); add_balance( to, quantity, from ); } void token::sub_balance( account_name owner, asset value ) { accounts from_acnts( _self, owner ); const auto& from = from_acnts.get( value.symbol.name(), "no balance object found" ); eosio_assert( from.balance.amount >= value.amount, "overdrawn balance" ); if( from.balance.amount == value.amount ) { from_acnts.erase( from ); } else { from_acnts.modify( from, owner, [&]( auto& a ) { a.balance -= value; }); } } void token::add_balance( account_name owner, asset value, account_name ram_payer ) { accounts to_acnts( _self, owner ); auto to = to_acnts.find( value.symbol.name() ); if( to == to_acnts.end() ) { to_acnts.emplace( ram_payer, [&]( auto& a ){ a.balance = value; }); } else { to_acnts.modify( to, 0, [&]( auto& a ) { a.balance += value; }); } } } /// namespace eosio EOSIO_ABI( eosio::token, (create)(issue)(transfer) )
示例命令:
$cleos push action eosio.token create '["usera","21000000.0000 DEMO"]' -p eosio.token usera $cleos push action eosio.token issue '["usera","21000000.0000 DEMO","issuance"]' -p usera $cleos push action eosio.token tranfser '["usera","userb","1000000.0000 DEMO","here you go"]' -p usera
表命令:
$cleos get table eosio.token DEMO stat { "rows": [{ "supply": "21000000.0000 DEMO" "max_supply": "2100000000.0000 DEMO" "issuer": "usera" } ], "more": false } $cleos get table eosio.token usera accounts { "rows": [{ "balance": "20000000.0000 DEMO" } ], "more": false } $cleos get table eosio.token userb accounts { "rows": [{ "balance": "10000000.0000 DEMO" } ], "more": false }
注意:本文是在Dawn4.1代碼發佈時編寫的。
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裏是原文理解eosio.token合約