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的结构,包含账户名、代币信息,这样每种代币就有了单独的账户体系:
accountKey, err := stub.CreateCompositeKey("account", []string{accountName, symbol})
可以根据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
来源:oschina
链接:https://my.oschina.net/u/4277346/blog/3285701