Unverified Commit 65ba0be8 authored by vipwzw's avatar vipwzw Committed by GitHub

Merge pull request #343 from linj-disanbo/token-mintage

Token mintage
parents 1d82de89 4f20a53d
......@@ -18,6 +18,8 @@ type tokenAutoTest struct {
TransferCaseArr []autotest.TransferCase `toml:"TransferCase,omitempty"`
WithdrawCaseArr []autotest.WithdrawCase `toml:"WithdrawCase,omitempty"`
TokenRevokeCaseArr []TokenRevokeCase `toml:"TokenRevokeCase,omitempty"`
TokenMintCaseArr []TokenMintCase `toml:"TokenMintCase,omitempty"`
TokenBurnCaseArr []TokenBurnCase `toml:"TokenBurnCase,omitempty"`
}
func init() {
......
......@@ -7,7 +7,7 @@ command = "account import_key -k 0xc21d38be90493512a5c2417d565269a8b23ce8152010e
[[TokenPreCreateCase]]
id = "tokenPre"
command = "send token precreate -f 0.01 -i testToken -n testToken -s TC -a 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv -t 100000 -p 1 -k 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv"
command = "send token precreate -c 1 -f 0.01 -i testToken -n testToken -s TC -a 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv -t 100000 -p 1 -k 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv"
dep = ["transForPrecreate", "import1"]
[[TokenPreCreateCase]]
......@@ -21,6 +21,19 @@ id = "tokenFinish"
command = "send token finish -a 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv -f 0.01 -s TC -k 0xc34b5d9d44ac7b754806f761d3d4d2c4fe5214f6b074c19f069c4f5c2a29c8cc"
dep = ["tokenPre"]
[[TokenMintCase]]
id = "tokenMint"
command = "send token mint -a 100 -s TC -k 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv"
dep = ["tokenFinish"]
amount = "100"
checkItem = ["balance"]
[[TokenBurnCase]]
id = "tokenBurn"
command = "send token burn -a 50 -s TC -k 12qyocayNF7Lv6C9qW4avxs2E7U41fKSfv"
dep = ["tokenMint"]
amount = "50"
checkItem = ["balance"]
#send to token for precreate
[[TransferCase]]
......
......@@ -6,6 +6,8 @@ package autotest
import (
"github.com/33cn/chain33/cmd/autotest/types"
"strconv"
)
// TokenPreCreateCase token precreatecase command
......@@ -59,3 +61,109 @@ func (testCase *TokenFinishCreateCase) SendCommand(packID string) (types.PackFun
return types.DefaultSend(testCase, &TokenFinishCreatePack{}, packID)
}
// TokenMintCase token mint case
type TokenMintCase struct {
types.BaseCase
Amount string `toml:"amount"`
}
// TokenMintPack token mint pack command
type TokenMintPack struct {
types.BaseCasePack
}
// SendCommand send command function of tokenfinishcreatecase
func (testCase *TokenMintCase) SendCommand(packID string) (types.PackFunc, error) {
return types.DefaultSend(testCase, &TokenMintPack{}, packID)
}
// GetCheckHandlerMap get check handle for map
func (pack *TokenMintPack) GetCheckHandlerMap() interface{} {
funcMap := make(types.CheckHandlerMapDiscard, 2)
funcMap["balance"] = pack.checkBalance
return funcMap
}
func (pack *TokenMintPack) checkBalance(txInfo map[string]interface{}) bool {
logArr := txInfo["receipt"].(map[string]interface{})["logs"].([]interface{})
logTokenBurn := logArr[1].(map[string]interface{})["log"].(map[string]interface{})
logAccBurn := logArr[2].(map[string]interface{})["log"].(map[string]interface{})
interCase := pack.TCase.(*TokenMintCase)
amount1, _ := strconv.ParseInt(interCase.Amount, 10, 64)
amount := amount1 * 1e8
pack.FLog.Info("MintDetails", "TestID", pack.PackID,
"TokenPrev", logTokenBurn["prev"].(map[string]interface{})["total"].(string),
"TokenCurr", logTokenBurn["current"].(map[string]interface{})["total"].(string),
"AccPrev", logAccBurn["prev"].(map[string]interface{})["balance"].(string),
"AccCurr", logAccBurn["current"].(map[string]interface{})["balance"].(string),
"amount", amount1)
totalCurrent := parseInt64(logTokenBurn["current"].(map[string]interface{})["total"])
totalPrev := parseInt64(logTokenBurn["prev"].(map[string]interface{})["total"])
accCurrent := parseInt64(logAccBurn["current"].(map[string]interface{})["balance"])
accPrev := parseInt64(logAccBurn["prev"].(map[string]interface{})["balance"])
return totalCurrent-amount == totalPrev && accCurrent-amount == accPrev
}
// TokenBurnCase token mint case
type TokenBurnCase struct {
types.BaseCase
Amount string `toml:"amount"`
}
// TokenBurnPack token mint pack command
type TokenBurnPack struct {
types.BaseCasePack
}
// SendCommand send command function
func (testCase *TokenBurnCase) SendCommand(packID string) (types.PackFunc, error) {
return types.DefaultSend(testCase, &TokenBurnPack{}, packID)
}
// GetCheckHandlerMap get check handle for map
func (pack *TokenBurnPack) GetCheckHandlerMap() interface{} {
funcMap := make(types.CheckHandlerMapDiscard, 2)
funcMap["balance"] = pack.checkBalance
return funcMap
}
func (pack *TokenBurnPack) checkBalance(txInfo map[string]interface{}) bool {
logArr := txInfo["receipt"].(map[string]interface{})["logs"].([]interface{})
logTokenBurn := logArr[1].(map[string]interface{})["log"].(map[string]interface{})
logAccBurn := logArr[2].(map[string]interface{})["log"].(map[string]interface{})
interCase := pack.TCase.(*TokenBurnCase)
amount1, _ := strconv.ParseInt(interCase.Amount, 10, 64)
amount := amount1 * 1e8
pack.FLog.Info("BurnDetails", "TestID", pack.PackID,
"TokenPrev", logTokenBurn["prev"].(map[string]interface{})["total"].(string),
"TokenCurr", logTokenBurn["current"].(map[string]interface{})["total"].(string),
"AccPrev", logAccBurn["prev"].(map[string]interface{})["balance"].(string),
"AccCurr", logAccBurn["current"].(map[string]interface{})["balance"].(string),
"amount", amount1)
totalCurrent := parseInt64(logTokenBurn["current"].(map[string]interface{})["total"])
totalPrev := parseInt64(logTokenBurn["prev"].(map[string]interface{})["total"])
accCurrent := parseInt64(logAccBurn["current"].(map[string]interface{})["balance"])
accPrev := parseInt64(logAccBurn["prev"].(map[string]interface{})["balance"])
return totalCurrent+amount == totalPrev && accCurrent+amount == accPrev
}
func parseInt64(s interface{}) int64 {
i, _ := strconv.ParseInt(s.(string), 10, 64)
return i
}
......@@ -42,6 +42,9 @@ func TokenCmd() *cobra.Command {
CreateRawTokenFinishTxCmd(),
CreateRawTokenRevokeTxCmd(),
CreateTokenTransferExecCmd(),
CreateRawTokenMintTxCmd(),
CreateRawTokenBurnTxCmd(),
GetTokenLogsCmd(),
)
return cmd
......@@ -465,3 +468,118 @@ func tokenRevoke(cmd *cobra.Command, args []string) {
ctx := jsonclient.NewRPCCtx(rpcLaddr, "token.CreateRawTokenRevokeTx", params, nil)
ctx.RunWithoutMarshal()
}
// CreateRawTokenMintTxCmd create raw token mintage transaction
func CreateRawTokenMintTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "mint",
Short: "Create a mint token transaction",
Run: tokenMint,
}
addTokenMintFlags(cmd)
return cmd
}
func addTokenMintFlags(cmd *cobra.Command) {
cmd.Flags().StringP("symbol", "s", "", "token symbol")
cmd.MarkFlagRequired("symbol")
cmd.Flags().Float64P("amount", "a", 0, "amount of mintage")
cmd.MarkFlagRequired("amount")
cmd.Flags().Float64P("fee", "f", 0, "token transaction fee")
}
func tokenMint(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
symbol, _ := cmd.Flags().GetString("symbol")
amount, _ := cmd.Flags().GetFloat64("amount")
params := &tokenty.TokenMint{
Symbol: symbol,
Amount: int64((amount+0.000001)*1e4) * 1e4,
}
ctx := jsonclient.NewRPCCtx(rpcLaddr, "token.CreateRawTokenMintTx", params, nil)
ctx.RunWithoutMarshal()
}
// CreateRawTokenBurnTxCmd create raw token burn transaction
func CreateRawTokenBurnTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "burn",
Short: "Create a burn token transaction",
Run: tokenBurn,
}
addTokenBurnFlags(cmd)
return cmd
}
func addTokenBurnFlags(cmd *cobra.Command) {
cmd.Flags().StringP("symbol", "s", "", "token symbol")
cmd.MarkFlagRequired("symbol")
cmd.Flags().Float64P("amount", "a", 0, "amount of burn")
cmd.MarkFlagRequired("amount")
cmd.Flags().Float64P("fee", "f", 0, "token transaction fee")
}
func tokenBurn(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
symbol, _ := cmd.Flags().GetString("symbol")
amount, _ := cmd.Flags().GetFloat64("amount")
params := &tokenty.TokenBurn{
Symbol: symbol,
Amount: int64((amount+0.000001)*1e4) * 1e4,
}
ctx := jsonclient.NewRPCCtx(rpcLaddr, "token.CreateRawTokenBurnTx", params, nil)
ctx.RunWithoutMarshal()
}
// GetTokenLogsCmd get logs of token
func GetTokenLogsCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "get_token_logs",
Short: "Get logs of token",
Run: getTokenLogs,
}
getTokenLogsFlags(cmd)
return cmd
}
func getTokenLogs(cmd *cobra.Command, args []string) {
rpcLaddr, _ := cmd.Flags().GetString("rpc_laddr")
paraName, _ := cmd.Flags().GetString("paraName")
symbol, _ := cmd.Flags().GetString("symbol")
var params rpctypes.Query4Jrpc
params.Execer = getRealExecName(paraName, "token")
params.FuncName = "GetTokenHistory"
params.Payload = types.MustPBToJSON(&types.ReqString{Data: symbol})
rpc, err := jsonclient.NewJSONClient(rpcLaddr)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
var res tokenty.ReplyTokenLogs
err = rpc.Call("Chain33.Query", params, &res)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
data, err := json.MarshalIndent(res, "", " ")
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(string(data))
}
func getTokenLogsFlags(cmd *cobra.Command) {
cmd.Flags().StringP("symbol", "s", "", "token symbol")
cmd.MarkFlagRequired("symbol")
}
......@@ -69,3 +69,13 @@ func (t *token) Exec_TransferToExec(payload *types.AssetsTransferToExec, tx *typ
}
return t.ExecTransWithdraw(db, tx, &tokenAction, index)
}
func (t *token) Exec_TokenMint(payload *tokenty.TokenMint, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newTokenAction(t, "", tx)
return action.mint(payload)
}
func (t *token) Exec_TokenBurn(payload *tokenty.TokenBurn, tx *types.Transaction, index int) (*types.Receipt, error) {
action := newTokenAction(t, "", tx)
return action.burn(payload)
}
......@@ -5,6 +5,7 @@
package executor
import (
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
tokenty "github.com/33cn/plugin/plugin/dapp/token/types"
)
......@@ -107,6 +108,19 @@ func (t *token) ExecDelLocal_TokenFinishCreate(payload *tokenty.TokenFinishCreat
var set []*types.KeyValue
set = append(set, &types.KeyValue{Key: prepareKey, Value: types.Encode(localToken)})
set = append(set, &types.KeyValue{Key: key, Value: nil})
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Del([]byte(txIndex))
if err != nil {
return nil, err
}
kv, err := table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
......@@ -123,3 +137,53 @@ func (t *token) ExecDelLocal_TokenRevokeCreate(payload *tokenty.TokenRevokeCreat
set = append(set, &types.KeyValue{Key: prepareKey, Value: types.Encode(localToken)})
return &types.LocalDBSet{KV: set}, nil
}
func (t *token) ExecDelLocal_TokenMint(payload *tokenty.TokenMint, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
localToken, err := loadLocalToken(payload.Symbol, tx.From(), tokenty.TokenStatusCreated, t.GetLocalDB())
if err != nil {
return nil, err
}
localToken = resetMint(localToken, t.GetHeight(), t.GetBlockTime(), payload.Amount)
key := calcTokenStatusKeyLocal(payload.Symbol, tx.From(), tokenty.TokenStatusCreated)
var set []*types.KeyValue
set = append(set, &types.KeyValue{Key: key, Value: types.Encode(localToken)})
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Del([]byte(txIndex))
if err != nil {
return nil, err
}
kv, err := table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
func (t *token) ExecDelLocal_TokenBurn(payload *tokenty.TokenBurn, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
localToken, err := loadLocalToken(payload.Symbol, tx.From(), tokenty.TokenStatusCreated, t.GetLocalDB())
if err != nil {
return nil, err
}
localToken = resetBurn(localToken, t.GetHeight(), t.GetBlockTime(), payload.Amount)
key := calcTokenStatusKeyLocal(payload.Symbol, tx.From(), tokenty.TokenStatusCreated)
var set []*types.KeyValue
set = append(set, &types.KeyValue{Key: key, Value: types.Encode(localToken)})
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Del([]byte(txIndex))
if err != nil {
return nil, err
}
kv, err := table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
......@@ -5,7 +5,10 @@
package executor
import (
"encoding/hex"
"github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/system/dapp"
"github.com/33cn/chain33/types"
tokenty "github.com/33cn/plugin/plugin/dapp/token/types"
)
......@@ -107,6 +110,19 @@ func (t *token) ExecLocal_TokenFinishCreate(payload *tokenty.TokenFinishCreate,
set = append(set, &types.KeyValue{Key: key, Value: types.Encode(localToken)})
kv := AddTokenToAssets(payload.Owner, t.GetLocalDB(), payload.Symbol)
set = append(set, kv...)
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Add(&tokenty.LocalLogs{Symbol: payload.Symbol, TxIndex: txIndex, ActionType: tokenty.TokenActionFinishCreate, TxHash: hex.EncodeToString(tx.Hash())})
if err != nil {
return nil, err
}
kv, err = table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
......@@ -182,6 +198,16 @@ func setRevoked(t *tokenty.LocalToken, height, time int64) *tokenty.LocalToken {
return t
}
func setMint(t *tokenty.LocalToken, height, time, amount int64) *tokenty.LocalToken {
t.Total = t.Total + amount
return t
}
func setBurn(t *tokenty.LocalToken, height, time, amount int64) *tokenty.LocalToken {
t.Total = t.Total - amount
return t
}
func resetCreated(t *tokenty.LocalToken) *tokenty.LocalToken {
t.CreatedTime = 0
t.CreatedHeight = 0
......@@ -195,3 +221,63 @@ func resetRevoked(t *tokenty.LocalToken) *tokenty.LocalToken {
t.Status = tokenty.TokenStatusPreCreated
return t
}
func resetMint(t *tokenty.LocalToken, height, time, amount int64) *tokenty.LocalToken {
t.Total = t.Total - amount
return t
}
func resetBurn(t *tokenty.LocalToken, height, time, amount int64) *tokenty.LocalToken {
t.Total = t.Total + amount
return t
}
func (t *token) ExecLocal_TokenMint(payload *tokenty.TokenMint, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
localToken, err := loadLocalToken(payload.Symbol, tx.From(), tokenty.TokenStatusCreated, t.GetLocalDB())
if err != nil {
return nil, err
}
localToken = setMint(localToken, t.GetHeight(), t.GetBlockTime(), payload.Amount)
var set []*types.KeyValue
key := calcTokenStatusKeyLocal(payload.Symbol, tx.From(), tokenty.TokenStatusCreated)
set = append(set, &types.KeyValue{Key: key, Value: types.Encode(localToken)})
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Add(&tokenty.LocalLogs{Symbol: payload.Symbol, TxIndex: txIndex, ActionType: tokenty.TokenActionMint, TxHash: "0x" + hex.EncodeToString(tx.Hash())})
if err != nil {
return nil, err
}
kv, err := table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
func (t *token) ExecLocal_TokenBurn(payload *tokenty.TokenBurn, tx *types.Transaction, receiptData *types.ReceiptData, index int) (*types.LocalDBSet, error) {
localToken, err := loadLocalToken(payload.Symbol, tx.From(), tokenty.TokenStatusCreated, t.GetLocalDB())
if err != nil {
return nil, err
}
localToken = setBurn(localToken, t.GetHeight(), t.GetBlockTime(), payload.Amount)
var set []*types.KeyValue
key := calcTokenStatusKeyLocal(payload.Symbol, tx.From(), tokenty.TokenStatusCreated)
set = append(set, &types.KeyValue{Key: key, Value: types.Encode(localToken)})
table := NewLogsTable(t.GetLocalDB())
txIndex := dapp.HeightIndexStr(t.GetHeight(), int64(index))
err = table.Add(&tokenty.LocalLogs{Symbol: payload.Symbol, TxIndex: txIndex, ActionType: tokenty.TokenActionBurn, TxHash: "0x" + hex.EncodeToString(tx.Hash())})
if err != nil {
return nil, err
}
kv, err := table.Save()
if err != nil {
return nil, err
}
set = append(set, kv...)
return &types.LocalDBSet{KV: set}, nil
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package executor
// 记录token 的更改记录,
// 包含创建完成, 铸币, 以后可能包含燃烧等
import (
dbm "github.com/33cn/chain33/common/db"
"github.com/33cn/chain33/common/db/table"
"github.com/33cn/chain33/types"
pty "github.com/33cn/plugin/plugin/dapp/token/types"
)
var opt_logs_table = &table.Option{
Prefix: "LODB-token",
Name: "logs",
Primary: "txIndex",
Index: []string{
"symbol",
},
}
// LogsRow row
type LogsRow struct {
*pty.LocalLogs
}
// NewOrderRow create row
func NewOrderRow() *LogsRow {
return &LogsRow{LocalLogs: nil}
}
// CreateRow create row
func (r *LogsRow) CreateRow() *table.Row {
return &table.Row{Data: &pty.LocalLogs{}}
}
// SetPayload set payload
func (r *LogsRow) SetPayload(data types.Message) error {
if d, ok := data.(*pty.LocalLogs); ok {
r.LocalLogs = d
return nil
}
return types.ErrTypeAsset
}
// Get get index key
func (r *LogsRow) Get(key string) ([]byte, error) {
switch key {
case "txIndex":
return []byte(r.TxIndex), nil
case "symbol":
return []byte(r.Symbol), nil
default:
return nil, types.ErrNotFound
}
}
// NewLogsTable create table
func NewLogsTable(kvdb dbm.KV) *table.Table {
rowMeta := NewOrderRow()
err := rowMeta.SetPayload(&pty.LocalLogs{})
if err != nil {
panic(err)
}
t, err := table.NewTable(rowMeta, kvdb, opt_logs_table)
if err != nil {
panic(err)
}
return t
}
func list(db dbm.KVDB, indexName string, data *pty.LocalLogs, count, direction int32) ([]*table.Row, error) {
query := NewLogsTable(db).GetQuery(db)
var primary []byte
if len(data.TxIndex) > 0 {
primary = []byte(data.TxIndex)
}
cur := &LogsRow{LocalLogs: data}
index, err := cur.Get(indexName)
if err != nil {
tokenlog.Error("query List failed", "key", string(primary), "param", data, "err", err)
return nil, err
}
tokenlog.Debug("query List dbg", "indexName", indexName, "index", string(index), "primary", primary, "count", count, "direction", direction)
rows, err := query.ListIndex(indexName, index, primary, count, direction)
if err != nil {
tokenlog.Error("query List failed", "key", string(primary), "param", data, "err", err)
return nil, err
}
if len(rows) == 0 {
return nil, types.ErrNotFound
}
return rows, nil
}
......@@ -69,3 +69,25 @@ func (t *token) Query_GetTxByToken(in *tokenty.ReqTokenTx) (types.Message, error
}
return t.getTxByToken(in)
}
// Query_GetTokenHistory 获取token 的变更历史
func (t *token) Query_GetTokenHistory(in *types.ReqString) (types.Message, error) {
if in == nil {
return nil, types.ErrInvalidParam
}
rows, err := list(t.GetLocalDB(), "symbol", &tokenty.LocalLogs{Symbol: in.Data}, -1, 0)
if err != nil {
tokenlog.Error("Query_GetTokenHistory", "err", err)
return nil, err
}
var replys tokenty.ReplyTokenLogs
for _, row := range rows {
o, ok := row.Data.(*tokenty.LocalLogs)
if !ok {
tokenlog.Error("Query_GetTokenHistory", "err", "bad row type")
return nil, types.ErrTypeAsset
}
replys.Logs = append(replys.Logs, o)
}
return &replys, nil
}
......@@ -157,6 +157,7 @@ func TestPrecreate(t *testing.T) {
Total: tokenAmount,
Price: tokenPrice,
Owner: addr,
Category: pty.CategoryMintBurnSupport,
}
precreate := &pty.TokenAction{
Ty: pty.TokenActionPreCreate,
......@@ -312,6 +313,78 @@ func TestQueryAsset(t *testing.T) {
}
func TestTokenMint(t *testing.T) {
if !isMainNetTest {
return
}
fmt.Println("TestTokenMint start")
defer fmt.Println("TestTokenMint end")
v := &pty.TokenAction_TokenMint{TokenMint: &pty.TokenMint{Symbol: tokenSym, Amount: transAmount}}
transfer := &pty.TokenAction{Value: v, Ty: pty.ActionTransfer}
tx := &types.Transaction{Execer: []byte(execName), Payload: types.Encode(transfer), Fee: fee, To: addrexec}
tx.Nonce = r.Int63()
tx.Sign(types.SECP256K1, privkey)
reply, err := mainClient.SendTransaction(context.Background(), tx)
if err != nil {
fmt.Println("err", err)
t.Error(err)
return
}
if !reply.IsOk {
fmt.Println("err = ", reply.GetMsg())
t.Error(ErrTest)
return
}
if !waitTx(tx.Hash()) {
t.Error(ErrTest)
return
}
}
func TestQueryTokenLogs(t *testing.T) {
if !isParaNetTest {
return
}
fmt.Println("TestQueryTokenLogs start")
defer fmt.Println("TestQueryTokenLogs end")
var req types.ChainExecutor
req.Driver = execName
req.FuncName = "GetTokenHistory"
req.Param = types.Encode(&types.ReqString{Data: tokenSym})
reply, err := paraClient.QueryChain(context.Background(), &req)
if err != nil {
fmt.Println(err)
t.Error(err)
return
}
if !reply.IsOk {
fmt.Println("Query reply err")
t.Error(ErrTest)
return
}
var res pty.ReplyTokenLogs
err = types.Decode(reply.Msg, &res)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, 2, len(res.Logs))
for _, l := range res.Logs {
fmt.Println(l.Symbol)
fmt.Println(l.TxHash)
fmt.Println(l.TxIndex)
fmt.Println(l.ActionType)
}
}
//***************************************************
//**************common actions for Test**************
//***************************************************
......
package executor
import (
"testing"
"github.com/33cn/chain33/account"
"github.com/33cn/chain33/common/address"
"github.com/33cn/chain33/types"
"github.com/33cn/chain33/util"
"github.com/33cn/chain33/common"
"github.com/33cn/chain33/common/crypto"
dbm "github.com/33cn/chain33/common/db"
pty "github.com/33cn/plugin/plugin/dapp/token/types"
"github.com/stretchr/testify/assert"
//"github.com/33cn/chain33/types/jsonpb"
)
type execEnv struct {
blockTime int64
blockHeight int64
difficulty uint64
}
var (
Symbol = "TEST"
AssetExecToken = "token"
AssetExecPara = "paracross"
PrivKeyA = "0x6da92a632ab7deb67d38c0f6560bcfed28167998f6496db64c258d5e8393a81b" // 1KSBd17H7ZK8iT37aJztFB22XGwsPTdwE4
PrivKeyB = "0x19c069234f9d3e61135fefbeb7791b149cdf6af536f26bebb310d4cd22c3fee4" // 1JRNjdEqp4LJ5fqycUBm9ayCKSeeskgMKR
PrivKeyC = "0x7a80a1f75d7360c6123c32a78ecf978c1ac55636f87892df38d8b85a9aeff115" // 1NLHPEcbTWWxxU3dGUZBhayjrCHD3psX7k
PrivKeyD = "0xcacb1f5d51700aea07fca2246ab43b0917d70405c65edea9b5063d72eb5c6b71" // 1MCftFynyvG2F4ED5mdHYgziDxx6vDrScs
Nodes = [][]byte{
[]byte("1KSBd17H7ZK8iT37aJztFB22XGwsPTdwE4"),
[]byte("1JRNjdEqp4LJ5fqycUBm9ayCKSeeskgMKR"),
[]byte("1NLHPEcbTWWxxU3dGUZBhayjrCHD3psX7k"),
[]byte("1MCftFynyvG2F4ED5mdHYgziDxx6vDrScs"),
}
)
func TestToken(t *testing.T) {
types.SetTitleOnlyForTest("chain33")
tokenTotal := int64(10000 * 1e8)
tokenBurn := int64(10 * 1e8)
tokenMint := int64(20 * 1e8)
total := int64(100000)
accountA := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[0]),
}
accountB := types.Account{
Balance: total,
Frozen: 0,
Addr: string(Nodes[1]),
}
execAddr := address.ExecAddress(pty.TokenX)
stateDB, _ := dbm.NewGoMemDB("1", "2", 100)
_, _, kvdb := util.CreateTestDB()
accA, _ := account.NewAccountDB(AssetExecPara, Symbol, stateDB)
accA.SaveExecAccount(execAddr, &accountA)
accB, _ := account.NewAccountDB(AssetExecPara, Symbol, stateDB)
accB.SaveExecAccount(execAddr, &accountB)
env := execEnv{
10,
types.GetDappFork(pty.TokenX, pty.ForkTokenSymbolWithNumberX),
1539918074,
}
// set config key
item := &types.ConfigItem{
Key: "mavl-manage-token-blacklist",
Value: &types.ConfigItem_Arr{
Arr: &types.ArrayConfig{Value: []string{"bty"}},
},
}
stateDB.Set([]byte(item.Key), types.Encode(item))
item2 := &types.ConfigItem{
Key: "mavl-manage-token-finisher",
Value: &types.ConfigItem_Arr{
Arr: &types.ArrayConfig{Value: []string{string(Nodes[0])}},
},
}
stateDB.Set([]byte(item2.Key), types.Encode(item2))
// create token
// 创建
//ty := pty.TokenType{}
p1 := &pty.TokenPreCreate{
Name: Symbol,
Symbol: Symbol,
Introduction: Symbol,
Total: tokenTotal,
Price: 0,
Owner: string(Nodes[0]),
Category: pty.CategoryMintBurnSupport,
}
//v, _ := types.PBToJSON(p1)
createTx, err := types.CallCreateTransaction(pty.TokenX, "TokenPreCreate", p1)
if err != nil {
t.Error("RPC_Default_Process", "err", err)
}
createTx, err = signTx(createTx, PrivKeyA)
if err != nil {
t.Error("RPC_Default_Process sign", "err", err)
}
exec := newToken()
exec.SetStateDB(stateDB)
exec.SetLocalDB(kvdb)
exec.SetEnv(env.blockHeight, env.blockTime, env.difficulty)
receipt, err := exec.Exec(createTx, int(1))
assert.Nil(t, err)
assert.NotNil(t, receipt)
t.Log(receipt)
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
receiptDate := &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err := exec.ExecLocal(createTx, receiptDate, int(1))
assert.Nil(t, err)
assert.NotNil(t, set)
p2 := &pty.TokenFinishCreate{
Symbol: Symbol,
Owner: string(Nodes[0]),
}
//v, _ := types.PBToJSON(p1)
createTx2, err := types.CallCreateTransaction(pty.TokenX, "TokenFinishCreate", p2)
if err != nil {
t.Error("RPC_Default_Process", "err", err)
}
createTx2, err = signTx(createTx2, PrivKeyA)
if err != nil {
t.Error("RPC_Default_Process sign", "err", err)
}
exec.SetEnv(env.blockHeight+1, env.blockTime+1, env.difficulty)
receipt, err = exec.Exec(createTx2, int(1))
assert.Nil(t, err)
assert.NotNil(t, receipt)
//t.Log(receipt)
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
accDB, _ := account.NewAccountDB(pty.TokenX, Symbol, stateDB)
accChcek := accDB.LoadAccount(string(Nodes[0]))
assert.Equal(t, tokenTotal, accChcek.Balance)
receiptDate = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(createTx2, receiptDate, int(1))
assert.Nil(t, err)
assert.NotNil(t, set)
// mint burn
p3 := &pty.TokenMint{
Symbol: Symbol,
Amount: tokenMint,
}
//v, _ := types.PBToJSON(p1)
createTx3, err := types.CallCreateTransaction(pty.TokenX, "TokenMint", p3)
if err != nil {
t.Error("RPC_Default_Process", "err", err)
}
createTx3, err = signTx(createTx3, PrivKeyA)
if err != nil {
t.Error("RPC_Default_Process sign", "err", err)
}
exec.SetEnv(env.blockHeight+2, env.blockTime+2, env.difficulty)
receipt, err = exec.Exec(createTx3, int(1))
assert.Nil(t, err)
assert.NotNil(t, receipt)
//t.Log(receipt)
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
accChcek = accDB.LoadAccount(string(Nodes[0]))
assert.Equal(t, tokenTotal+tokenMint, accChcek.Balance)
receiptDate = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(createTx3, receiptDate, int(1))
assert.Nil(t, err)
assert.NotNil(t, set)
p4 := &pty.TokenBurn{
Symbol: Symbol,
Amount: tokenBurn,
}
//v, _ := types.PBToJSON(p1)
createTx4, err := types.CallCreateTransaction(pty.TokenX, "TokenBurn", p4)
if err != nil {
t.Error("RPC_Default_Process", "err", err)
}
createTx4, err = signTx(createTx4, PrivKeyA)
if err != nil {
t.Error("RPC_Default_Process sign", "err", err)
}
exec.SetEnv(env.blockHeight+1, env.blockTime+1, env.difficulty)
receipt, err = exec.Exec(createTx4, int(1))
assert.Nil(t, err)
assert.NotNil(t, receipt)
//t.Log(receipt)
for _, kv := range receipt.KV {
stateDB.Set(kv.Key, kv.Value)
}
accChcek = accDB.LoadAccount(string(Nodes[0]))
assert.Equal(t, tokenTotal+tokenMint-tokenBurn, accChcek.Balance)
receiptDate = &types.ReceiptData{Ty: receipt.Ty, Logs: receipt.Logs}
set, err = exec.ExecLocal(createTx4, receiptDate, int(1))
assert.Nil(t, err)
assert.NotNil(t, set)
}
func signTx(tx *types.Transaction, hexPrivKey string) (*types.Transaction, error) {
signType := types.SECP256K1
c, err := crypto.New(types.GetSignName(pty.TokenX, signType))
if err != nil {
return tx, err
}
bytes, err := common.FromHex(hexPrivKey[:])
if err != nil {
return tx, err
}
privKey, err := c.PrivKeyFromBytes(bytes)
if err != nil {
return tx, err
}
tx.Sign(int32(signType), privKey)
return tx, nil
}
......@@ -40,7 +40,10 @@ func newTokenDB(preCreate *pty.TokenPreCreate, creator string, height int64) *to
func (t *tokenDB) save(db dbm.KV, key []byte) {
set := t.getKVSet(key)
for i := 0; i < len(set); i++ {
db.Set(set[i].GetKey(), set[i].Value)
err := db.Set(set[i].GetKey(), set[i].Value)
if err != nil {
panic(err)
}
}
}
......@@ -59,6 +62,48 @@ func (t *tokenDB) getKVSet(key []byte) (kvset []*types.KeyValue) {
return kvset
}
func loadTokenDB(db dbm.KV, symbol string) (*tokenDB, error) {
token, err := db.Get(calcTokenKey(symbol))
if err != nil {
tokenlog.Error("tokendb load ", "Can't get token form db for token", symbol)
return nil, pty.ErrTokenNotExist
}
var t pty.Token
err = types.Decode(token, &t)
if err != nil {
tokenlog.Error("tokendb load", "Can't decode token info", symbol)
return nil, err
}
return &tokenDB{t}, nil
}
func (t *tokenDB) mint(db dbm.KV, addr string, amount int64) ([]*types.KeyValue, []*types.ReceiptLog, error) {
if t.token.Owner != addr {
return nil, nil, types.ErrNotAllow
}
if t.token.Total+amount > types.MaxTokenBalance {
return nil, nil, types.ErrAmount
}
prevToken := t.token
t.token.Total += amount
kvs := append(t.getKVSet(calcTokenKey(t.token.Symbol)), t.getKVSet(calcTokenAddrNewKeyS(t.token.Symbol, t.token.Owner))...)
logs := []*types.ReceiptLog{{Ty: pty.TyLogTokenMint, Log: types.Encode(&pty.ReceiptTokenAmount{Prev: &prevToken, Current: &t.token})}}
return kvs, logs, nil
}
func (t *tokenDB) burn(db dbm.KV, amount int64) ([]*types.KeyValue, []*types.ReceiptLog, error) {
if t.token.Total < amount {
return nil, nil, types.ErrNoBalance
}
prevToken := t.token
t.token.Total -= amount
kvs := append(t.getKVSet(calcTokenKey(t.token.Symbol)), t.getKVSet(calcTokenAddrNewKeyS(t.token.Symbol, t.token.Owner))...)
logs := []*types.ReceiptLog{{Ty: pty.TyLogTokenBurn, Log: types.Encode(&pty.ReceiptTokenAmount{Prev: &prevToken, Current: &t.token})}}
return kvs, logs, nil
}
func getTokenFromDB(db dbm.KV, symbol string, owner string) (*pty.Token, error) {
key := calcTokenAddrKeyS(symbol, owner)
value, err := db.Get(key)
......@@ -468,3 +513,87 @@ func validSymbolWithHeight(cs []byte, height int64) bool {
}
return validSymbolOriginal(cs)
}
// 铸币不可控, 也是麻烦。 2选1
// 1. 谁可以发起
// 2. 是否需要审核 这个会增加管理的成本
// 现在实现选择 1
func (action *tokenAction) mint(mint *pty.TokenMint) (*types.Receipt, error) {
if mint == nil {
return nil, types.ErrInvalidParam
}
if mint.GetAmount() < 0 || mint.GetAmount() > types.MaxTokenBalance || mint.GetSymbol() == "" {
return nil, types.ErrInvalidParam
}
tokendb, err := loadTokenDB(action.db, mint.GetSymbol())
if err != nil {
return nil, err
}
if tokendb.token.Category&pty.CategoryMintBurnSupport == 0 {
tokenlog.Error("Can't mint category", "category", tokendb.token.Category, "support", pty.CategoryMintBurnSupport)
return nil, types.ErrNotSupport
}
kvs, logs, err := tokendb.mint(action.db, action.fromaddr, mint.Amount)
if err != nil {
tokenlog.Error("token mint ", "symbol", mint.GetSymbol(), "error", err, "from", action.fromaddr, "owner", tokendb.token.Owner)
return nil, err
}
tokenAccount, err := account.NewAccountDB("token", mint.GetSymbol(), action.db)
if err != nil {
return nil, err
}
tokenlog.Debug("mint", "token.Owner", mint.Symbol, "token.GetTotal()", mint.Amount)
receipt, err := tokenAccount.Mint(action.fromaddr, mint.Amount)
if err != nil {
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
return &types.Receipt{Ty: types.ExecOk, KV: kvs, Logs: logs}, nil
}
func (action *tokenAction) burn(burn *pty.TokenBurn) (*types.Receipt, error) {
if burn == nil {
return nil, types.ErrInvalidParam
}
if burn.GetAmount() < 0 || burn.GetAmount() > types.MaxTokenBalance || burn.GetSymbol() == "" {
return nil, types.ErrInvalidParam
}
tokendb, err := loadTokenDB(action.db, burn.GetSymbol())
if err != nil {
return nil, err
}
if tokendb.token.Category&pty.CategoryMintBurnSupport == 0 {
tokenlog.Error("Can't burn category", "category", tokendb.token.Category, "support", pty.CategoryMintBurnSupport)
return nil, types.ErrNotSupport
}
kvs, logs, err := tokendb.burn(action.db, burn.Amount)
if err != nil {
tokenlog.Error("token burn ", "symbol", burn.GetSymbol(), "error", err, "from", action.fromaddr, "owner", tokendb.token.Owner)
return nil, err
}
tokenAccount, err := account.NewAccountDB("token", burn.GetSymbol(), action.db)
if err != nil {
return nil, err
}
tokenlog.Debug("burn", "token.Owner", burn.Symbol, "token.GetTotal()", burn.Amount)
receipt, err := tokenAccount.Burn(action.fromaddr, burn.Amount)
if err != nil {
return nil, err
}
logs = append(logs, receipt.Logs...)
kvs = append(kvs, receipt.KV...)
return &types.Receipt{Ty: types.ExecOk, KV: kvs, Logs: logs}, nil
}
......@@ -172,7 +172,10 @@ func updateAddrReciver(cachedb dbm.KVDB, token string, addr string, amount int64
} else {
recv -= amount
}
setAddrReciver(cachedb, token, addr, recv)
err = setAddrReciver(cachedb, token, addr, recv)
if err != nil {
return nil, err
}
//keyvalue
return getAddrReciverKV(token, addr, recv), nil
}
......@@ -15,6 +15,8 @@ message TokenAction {
AssetsWithdraw withdraw = 5;
AssetsGenesis genesis = 6;
AssetsTransferToExec transferToExec = 8;
TokenMint tokenMint = 9;
TokenBurn tokenBurn = 10;
}
int32 Ty = 7;
}
......@@ -40,6 +42,16 @@ message TokenRevokeCreate {
string owner = 2;
}
message TokenMint {
string symbol = 1;
int64 amount = 2;
}
message TokenBurn {
string symbol = 1;
int64 amount = 2;
}
// state db
message Token {
string name = 1;
......@@ -60,6 +72,11 @@ message ReceiptToken {
int32 status = 3;
}
message ReceiptTokenAmount {
Token prev = 1;
Token current = 2;
}
// local
message LocalToken {
string name = 1;
......@@ -82,6 +99,13 @@ message LocalToken {
int32 category = 17;
}
message LocalLogs {
string symbol = 1;
string txIndex = 2;
int32 actionType = 3;
string txHash = 4;
}
// query
message ReqTokens {
bool queryAll = 1;
......@@ -142,6 +166,10 @@ message ReqTokenTx {
string addr = 7;
}
message ReplyTokenLogs {
repeated LocalLogs logs = 1;
}
service token {
// token 对外提供服务的接口
//区块链接口
......
......@@ -121,3 +121,29 @@ func (c *Jrpc) CreateRawTokenRevokeTx(param *tokenty.TokenRevokeCreate, result *
*result = hex.EncodeToString(data)
return nil
}
// CreateRawTokenMintTx 创建未签名的mint Token交易
func (c *Jrpc) CreateRawTokenMintTx(param *tokenty.TokenMint, result *interface{}) error {
if param == nil || param.Symbol == "" || param.Amount <= 0 {
return types.ErrInvalidParam
}
data, err := types.CallCreateTx(types.ExecName(tokenty.TokenX), "TokenMint", param)
if err != nil {
return err
}
*result = hex.EncodeToString(data)
return nil
}
// CreateRawTokenBurnTx 创建未签名的 burn Token交易
func (c *Jrpc) CreateRawTokenBurnTx(param *tokenty.TokenBurn, result *interface{}) error {
if param == nil || param.Symbol == "" || param.Amount <= 0 {
return types.ErrInvalidParam
}
data, err := types.CallCreateTx(types.ExecName(tokenty.TokenX), "TokenBurn", param)
if err != nil {
return err
}
*result = hex.EncodeToString(data)
return nil
}
......@@ -19,6 +19,10 @@ const (
TokenActionRevokeCreate = 9
// TokenActionTransferToExec for token transfer to exec
TokenActionTransferToExec = 11
// TokenActionMint for token mint
TokenActionMint = 12
// TokenActionBurn for token burn
TokenActionBurn = 13
)
// token status
......@@ -72,6 +76,10 @@ const (
TyLogTokenGenesisTransfer = 321
// TyLogTokenGenesisDeposit log for token genesis deposit
TyLogTokenGenesisDeposit = 322
// TyLogTokenMint log for token mint
TyLogTokenMint = 323
// TyLogTokenBurn log for token burn
TyLogTokenBurn = 324
)
const (
......@@ -82,3 +90,8 @@ const (
// TokenIntroLenLimit token introduction length limit
TokenIntroLenLimit = 1024
)
const (
// CategoryMintBurnSupport support mint & burn
CategoryMintBurnSupport = 1 << iota
)
This diff is collapsed.
......@@ -57,6 +57,8 @@ func (t *TokenType) GetTypeMap() map[string]int32 {
"TokenFinishCreate": TokenActionFinishCreate,
"TokenRevokeCreate": TokenActionRevokeCreate,
"TransferToExec": TokenActionTransferToExec,
"TokenMint": TokenActionMint,
"TokenBurn": TokenActionBurn,
}
}
......@@ -75,6 +77,8 @@ func (t *TokenType) GetLogMap() map[int64]*types.LogInfo {
TyLogPreCreateToken: {Ty: reflect.TypeOf(ReceiptToken{}), Name: "LogPreCreateToken"},
TyLogFinishCreateToken: {Ty: reflect.TypeOf(ReceiptToken{}), Name: "LogFinishCreateToken"},
TyLogRevokeCreateToken: {Ty: reflect.TypeOf(ReceiptToken{}), Name: "LogRevokeCreateToken"},
TyLogTokenMint: {Ty: reflect.TypeOf(ReceiptTokenAmount{}), Name: "LogMintToken"},
TyLogTokenBurn: {Ty: reflect.TypeOf(ReceiptTokenAmount{}), Name: "LogBurnToken"},
}
}
......
......@@ -601,3 +601,4 @@ func TestDB_Burn(t *testing.T) {
t.Logf("Token mint addr balance [%d]", tokenCoin.LoadAccount(addr1).Balance)
require.Equal(t, int64(1000*1e8-10*1e8), tokenCoin.LoadAccount(addr1).Balance)
}
......@@ -80,4 +80,4 @@ message ReqAllExecBalance {
string stateHash = 3;
string asset_exec = 4;
string asset_symbol = 5;
}
\ No newline at end of file
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment