1. 應用場景
代币系統需要支援新種類代币發行、代币轉賬,額度查詢,代币增發,代币回收、賬戶當機,鎖倉等功能。
代币增發後轉入coinbase賬戶,coinbase賬戶與普通賬戶之間可以互相轉賬。這樣就實作了代币流通。
2. 資料的生命周期
代币(token)資料内容包括代币簡稱、代币名稱、代币發行者、總供應量、鎖倉辨別等資訊;
賬戶(account)資料内容包括賬戶名、賬戶的代币類型、當機辨別、餘額等資訊。
代币發行方可以發行代币、增發代币、回收代币、鎖倉、當機賬戶;
使用者可以将代币轉賬從自己的賬戶給别人的賬戶。
3. 資料結構
- token的key為:
TokenSymbol
- token的value結構為:
type Token struct {
TokenSymbol string `json:"TokenSymbol"`
TokenName string `json:"TokenName"`
Owner string `json:"Owner"`
TotalSupply int64 `json:"TotalSupply"`
Lock bool `json:"Lock"`
}
- account采用複合key的結構,包含賬戶名、代币資訊,這樣每種代币就有了單獨的賬戶體系:
可以根據key值字首進行範圍查詢,查詢出賬戶名擁有的各種代币。
- account的value結構為:
type Account struct {
AccountName string `json:"AccountName"`
TokenSymbol string `json:"TokenSymbol"`
Frozen bool `json:"Frozen"`
Balance int64 `json:"Balance"`
}
4. 合約源碼
package main
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
)
// Token 代币資料
type Token struct {
TokenSymbol string `json:"TokenSymbol"`
TokenName string `json:"TokenName"`
Owner string `json:"Owner"`
TotalSupply int64 `json:"TotalSupply"`
Lock bool `json:"Lock"`
}
// Account 代币賬戶資料
type Account struct {
AccountName string `json:"AccountName"`
TokenSymbol string `json:"TokenSymbol"`
Frozen bool `json:"Frozen"`
Balance int64 `json:"Balance"`
}
func (token *Token) transfer(from *Account, to *Account, amount int64) error {
if token.Lock {
return errors.New("鎖倉狀态,無法轉賬")
}
if from.Frozen {
return errors.New("From 賬戶已被當機")
}
if to.Frozen {
return errors.New("To 賬戶已被當機")
}
if from.Balance >= amount {
from.Balance -= amount
to.Balance += amount
} else {
return errors.New("From 賬戶餘額不足")
}
return nil
}
func (token *Token) mint(amount int64, account *Account) error {
if token.Lock {
return errors.New("鎖倉狀态,無法增發代币")
}
token.TotalSupply += amount
account.Balance += amount
return nil
}
func (token *Token) burn(amount int64, account *Account) error {
if token.Lock {
return errors.New("鎖倉狀态,無法回收代币")
}
if account.Balance >= amount {
account.Balance -= amount
token.TotalSupply -= amount
}
return nil
}
func (token *Token) setLock(lock bool) {
token.Lock = lock
}
func (account *Account) setFrozen(status bool) {
account.Frozen = status
}
func (account *Account) balance() int64 {
return account.Balance
}
// Contract ...
type Contract struct {
}
// Init ...
func (tc *Contract) Init(stub shim.ChaincodeStubInterface) pb.Response {
return shim.Success(nil)
}
// Invoke ...
func (tc *Contract) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
functions, args := stub.GetFunctionAndParameters()
if functions == "issueNewToken" {
return tc.issueNewToken(stub, args)
} else if functions == "mintToken" {
return tc.mintToken(stub, args)
} else if functions == "burnToken" {
return tc.burnToken(stub, args)
} else if functions == "transferToken" {
return tc.transferToken(stub, args)
} else if functions == "setLock" {
return tc.setLock(stub, args)
} else if functions == "frozenAccount" {
return tc.frozenAccount(stub, args)
} else if functions == "queryToken" {
return tc.queryToken(stub, args)
} else if functions == "queryAccount" {
return tc.queryAccount(stub, args)
}
return shim.Error("Invalid function name in Contract.")
}
func (tc *Contract) issueNewToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}
tokenSymbol := args[0]
tokenName := args[1]
supply, err := strconv.ParseInt(args[2], 10, 64)
if err != nil {
return shim.Error(err.Error())
}
// 判斷token是否已存在
exist, err := stub.GetState(tokenSymbol)
if exist != nil {
return shim.Error("Token existed!")
}
token := &Token{
TokenSymbol: tokenSymbol,
TokenName: tokenName,
Owner: "coinbase",
TotalSupply: supply,
Lock: false,
}
account := Account{
AccountName: token.Owner,
TokenSymbol: tokenSymbol,
Frozen: false,
Balance: supply,
}
tokenAsBytes, err := json.Marshal(token)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(tokenSymbol, tokenAsBytes)
if err != nil {
return shim.Error(err.Error())
}
accountKey, err := stub.CreateCompositeKey("account", []string{token.Owner, tokenSymbol})
if err != nil {
return shim.Error(err.Error())
}
acountAsBytes, err := json.Marshal(account)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(accountKey, acountAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) mintToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2.")
}
symbol := args[0]
amount, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return shim.Error(err.Error())
}
tokenAsBytes, err := stub.GetState(symbol)
if err != nil {
return shim.Error(err.Error())
}
if tokenAsBytes == nil {
return shim.Error("Entity not found")
}
token := Token{}
err = json.Unmarshal(tokenAsBytes, &token)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + symbol + "\"}")
}
accountKey, err := stub.CreateCompositeKey("account", []string{token.Owner, symbol})
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err := stub.GetState(accountKey)
if err != nil {
return shim.Error(err.Error())
}
if accountAsBytes == nil {
return shim.Error("Entity not found")
}
account := Account{}
err = json.Unmarshal(accountAsBytes, &account)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + token.Owner + symbol + "\"}")
}
err = token.mint(amount, &account)
if err != nil {
return shim.Error(err.Error())
}
tokenAsBytes, err = json.Marshal(token)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(symbol, tokenAsBytes)
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err = json.Marshal(account)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(accountKey, accountAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) burnToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3.")
}
symbol := args[0]
amount, err := strconv.ParseInt(args[1], 10, 64)
if err != nil {
return shim.Error(err.Error())
}
accountName := args[2]
tokenAsBytes, err := stub.GetState(symbol)
if err != nil {
return shim.Error(err.Error())
}
if tokenAsBytes == nil {
return shim.Error("Entity not found")
}
token := Token{}
err = json.Unmarshal(tokenAsBytes, &token)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + symbol + "\"}")
}
accountKey, err := stub.CreateCompositeKey("account", []string{accountName, symbol})
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err := stub.GetState(accountKey)
if err != nil {
return shim.Error(err.Error())
}
if accountAsBytes == nil {
return shim.Error("Entity not found")
}
account := Account{}
err = json.Unmarshal(accountAsBytes, &account)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + accountName + symbol + "\"}")
}
err = token.burn(amount, &account)
if err != nil {
return shim.Error(err.Error())
}
tokenAsBytes, err = json.Marshal(token)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(symbol, tokenAsBytes)
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err = json.Marshal(account)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(accountKey, accountAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) transferToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
symbol := args[0]
from := args[1]
to := args[2]
amount, err := strconv.ParseInt(args[3], 10, 64)
if err != nil {
return shim.Error(err.Error())
}
if amount <= 0 {
return shim.Error("Incorrect amount!")
}
// 源賬戶處理
fromKey, err := stub.CreateCompositeKey("account", []string{from, symbol})
if err != nil {
return shim.Error(err.Error())
}
fromAsBytes, err := stub.GetState(fromKey)
if err != nil {
return shim.Error(err.Error())
}
if fromAsBytes == nil {
return shim.Error("Entity not found")
}
fromAccount := Account{}
err = json.Unmarshal(fromAsBytes, &fromAccount)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + from + symbol + "\"}")
}
// 目的賬戶處理
toKey, err := stub.CreateCompositeKey("account", []string{to, symbol})
if err != nil {
return shim.Error(err.Error())
}
toAsBytes, err := stub.GetState(toKey)
if err != nil {
return shim.Error(err.Error())
}
toAccount := Account{}
if toAsBytes == nil {
toAccount.AccountName = to
toAccount.Balance = 0
toAccount.Frozen = false
toAccount.TokenSymbol = symbol
} else {
err = json.Unmarshal(toAsBytes, &toAccount)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + to + symbol + "\"}")
}
}
// token處理
tokenAsBytes, err := stub.GetState(symbol)
if err != nil {
return shim.Error(err.Error())
}
if tokenAsBytes == nil {
return shim.Error("Entity not found")
}
token := &Token{}
err = json.Unmarshal(tokenAsBytes, &token)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + symbol + "\"}")
}
// 轉賬
err = token.transfer(&fromAccount, &toAccount, amount)
if err != nil {
return shim.Error(err.Error())
}
// 入庫
fromAsBytes, err = json.Marshal(fromAccount)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(fromKey, fromAsBytes)
if err != nil {
return shim.Error(err.Error())
}
toAsBytes, err = json.Marshal(toAccount)
if err != nil {
return shim.Error(err.Error())
}
err = stub.PutState(toKey, toAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) setLock(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
symbol := args[0]
lock, err := strconv.ParseBool(args[1])
if err != nil {
return shim.Error(err.Error())
}
tokenAsBytes, err := stub.GetState(symbol)
if err != nil {
return shim.Error(err.Error())
}
if tokenAsBytes == nil {
return shim.Error("Entity not found")
}
token := Token{}
err = json.Unmarshal(tokenAsBytes, &token)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + symbol + "\"}")
}
token.setLock(lock)
tokenAsBytes, _ = json.Marshal(token)
err = stub.PutState(symbol, tokenAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) frozenAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 3 {
return shim.Error("Incorrect number of arguments. Expecting 3")
}
symbol := args[0]
accountName := args[1]
frozen, err := strconv.ParseBool(args[2])
if err != nil {
return shim.Error(err.Error())
}
accountKey, err := stub.CreateCompositeKey("account", []string{accountName, symbol})
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err := stub.GetState(accountKey)
if err != nil {
return shim.Error(err.Error())
}
if accountAsBytes == nil {
return shim.Error("Entity not found")
}
account := Account{}
err = json.Unmarshal(accountAsBytes, &account)
if err != nil {
return shim.Error("{\"Error\":\"Failed to decode JSON of: " + accountName + symbol + "\"}")
}
account.setFrozen(frozen)
accountAsBytes, _ = json.Marshal(account)
err = stub.PutState(accountKey, accountAsBytes)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(nil)
}
func (tc *Contract) queryToken(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 1 {
return shim.Error("Incorrect number of arguments. Expecting 1")
}
symbol := args[0]
tokenAsBytes, err := stub.GetState(symbol)
if err != nil {
return shim.Error(err.Error())
}
if tokenAsBytes == nil {
return shim.Error("{\"Error\":\"Nil value for " + symbol + "\"}")
}
fmt.Printf("Query Response:%s\n", string(tokenAsBytes))
return shim.Success(tokenAsBytes)
}
func (tc *Contract) queryAccount(stub shim.ChaincodeStubInterface, args []string) pb.Response {
if len(args) != 2 {
return shim.Error("Incorrect number of arguments. Expecting 2")
}
accountName := args[0]
symbol := args[1]
accountKey, err := stub.CreateCompositeKey("account", []string{accountName, symbol})
if err != nil {
return shim.Error(err.Error())
}
accountAsBytes, err := stub.GetState(accountKey)
if err != nil {
return shim.Error(err.Error())
}
if accountAsBytes == nil {
return shim.Error("{\"Error\":\"Nil value for " + accountName + symbol + "\"}")
}
fmt.Printf("Query Response:%s\n", string(accountAsBytes))
return shim.Success(accountAsBytes)
}
func main() {
err := shim.Start(new(Contract))
if err != nil {
fmt.Printf("Error starting Contract chaincode: %s", err)
}
}
參考:
http://www.netkiller.cn/blockchain/hyperledger/chaincode/chaincode.example.html#chaincode.token