Fabric 智能合约——token(代币)交易

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

发布了49 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ice_fire_x/article/details/105602284