天天看點

Fabric 智能合約——token(代币)交易1. 應用場景2. 資料的生命周期3. 資料結構4. 合約源碼

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