types/rational: use encoding/json
minor fixes working on compiling
This commit is contained in:
parent
8e3f8319af
commit
af6c1a3f02
|
@ -109,6 +109,10 @@ func ErrInvalidCoins(msg string) Error {
|
||||||
return newError(CodeInvalidCoins, msg)
|
return newError(CodeInvalidCoins, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrInvalidCoins(coins Coins) Error {
|
||||||
|
return newError(CodeInvalidCoins, coins.String())
|
||||||
|
}
|
||||||
|
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
// Error & sdkError
|
// Error & sdkError
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,12 +16,21 @@ import (
|
||||||
// __| |_ |_
|
// __| |_ |_
|
||||||
|
|
||||||
// Rat - extend big.Rat
|
// Rat - extend big.Rat
|
||||||
|
// NOTE: never use new(Rat) or else
|
||||||
|
// we will panic unmarshalling into the
|
||||||
|
// nil embedded big.Rat
|
||||||
type Rat struct {
|
type Rat struct {
|
||||||
*big.Rat `json:"rat"`
|
*big.Rat `json:"rat"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rational - big Rat with additional functionality
|
type Rational = Rat
|
||||||
type Rational interface {
|
|
||||||
|
// RationalInterface - big Rat with additional functionality
|
||||||
|
// NOTE: we only have one implementation of this interface
|
||||||
|
// and don't use it anywhere, but it might come in handy
|
||||||
|
// if we want to provide Rational types that include
|
||||||
|
// the units of the value in the type system.
|
||||||
|
type RationalInterface interface {
|
||||||
GetRat() *big.Rat
|
GetRat() *big.Rat
|
||||||
Num() int64
|
Num() int64
|
||||||
Denom() int64
|
Denom() int64
|
||||||
|
@ -200,6 +212,37 @@ func (r Rat) Round(precisionFactor int64) Rational {
|
||||||
//return nil
|
//return nil
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//nolint
|
var ratCdc JSONCodec // TODO wire.Codec
|
||||||
func (r Rat) MarshalJSON() ([]byte, error) { return r.MarshalText() }
|
|
||||||
func (r *Rat) UnmarshalJSON(data []byte) (err error) { return r.UnmarshalText(data) }
|
// Hack to just use json.Marshal for everything until
|
||||||
|
// we update for amino
|
||||||
|
type JSONCodec struct{}
|
||||||
|
|
||||||
|
func (jc JSONCodec) MarshalJSON(o interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jc JSONCodec) UnmarshalJSON(bz []byte, o interface{}) error {
|
||||||
|
return json.Unmarshal(bz, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wraps r.MarshalText() in quotes to make it a valid JSON string.
|
||||||
|
func (r Rat) MarshalJSON() ([]byte, error) {
|
||||||
|
bz, err := r.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return bz, err
|
||||||
|
}
|
||||||
|
return []byte(fmt.Sprintf(`"%s"`, bz)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Requires a valid JSON string - strings quotes and calls UnmarshalText
|
||||||
|
func (r *Rat) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
quote := []byte(`"`)
|
||||||
|
if len(data) < 2 ||
|
||||||
|
!bytes.HasPrefix(data, quote) ||
|
||||||
|
!bytes.HasSuffix(data, quote) {
|
||||||
|
return fmt.Errorf("JSON encoded Rat must be a quote-delimitted string")
|
||||||
|
}
|
||||||
|
data = bytes.Trim(data, `"`)
|
||||||
|
return r.UnmarshalText(data)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -193,41 +192,44 @@ func TestRound(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestZeroSerializationJSON(t *testing.T) {
|
func TestZeroSerializationJSON(t *testing.T) {
|
||||||
var r Rat
|
r := NewRat(0, 1)
|
||||||
err := json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":1}"), &r)
|
err := r.UnmarshalJSON([]byte(`"0/1"`))
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
err = json.Unmarshal([]byte("{\"numerator\":0,\"denominator\":0}"), &r)
|
err = r.UnmarshalJSON([]byte(`"0/0"`))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
err = json.Unmarshal([]byte("{\"numerator\":1,\"denominator\":0}"), &r)
|
err = r.UnmarshalJSON([]byte(`"1/0"`))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
err = json.Unmarshal([]byte("{}"), &r)
|
err = r.UnmarshalJSON([]byte(`"{}"`))
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSerializationJSON(t *testing.T) {
|
func TestSerializationJSON(t *testing.T) {
|
||||||
r := NewRat(1, 3)
|
r := NewRat(1, 3)
|
||||||
|
|
||||||
rMarshal, err := json.Marshal(r)
|
bz, err := r.MarshalText()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
var rUnmarshal Rat
|
r2 := NewRat(0, 1)
|
||||||
err = json.Unmarshal(rMarshal, &rUnmarshal)
|
err = r2.UnmarshalText(bz)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal)
|
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSerializationGoWire(t *testing.T) {
|
func TestSerializationGoWire(t *testing.T) {
|
||||||
r := NewRat(1, 3)
|
r := NewRat(1, 3)
|
||||||
|
|
||||||
rMarshal, err := ratCdc.MarshalJSON(r)
|
bz, err := ratCdc.MarshalJSON(r)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
var rUnmarshal Rat
|
bz, err = r.MarshalJSON()
|
||||||
err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal)
|
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.True(t, r.Equal(rUnmarshal), "original: %v, unmarshalled: %v", r, rUnmarshal)
|
r2 := NewRat(0, 1)
|
||||||
|
err = ratCdc.UnmarshalJSON(bz, &r2)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assert.True(t, r.Equal(r2), "original: %v, unmarshalled: %v", r, r2)
|
||||||
}
|
}
|
||||||
|
|
||||||
type testEmbedStruct struct {
|
type testEmbedStruct struct {
|
||||||
|
@ -237,38 +239,18 @@ type testEmbedStruct struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmbeddedStructSerializationGoWire(t *testing.T) {
|
func TestEmbeddedStructSerializationGoWire(t *testing.T) {
|
||||||
r := testEmbedStruct{"foo", 10, NewRat(1, 3)}
|
obj := testEmbedStruct{"foo", 10, NewRat(1, 3)}
|
||||||
|
|
||||||
rMarshal, err := ratCdc.MarshalJSON(r)
|
bz, err := ratCdc.MarshalJSON(obj)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
var rUnmarshal testEmbedStruct
|
var obj2 testEmbedStruct
|
||||||
err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal)
|
obj2.Field3 = NewRat(0, 1) // ... needs to be initialized
|
||||||
|
err = ratCdc.UnmarshalJSON(bz, &obj2)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, r.Field1, rUnmarshal.Field1)
|
assert.Equal(t, obj.Field1, obj2.Field1)
|
||||||
assert.Equal(t, r.Field2, rUnmarshal.Field2)
|
assert.Equal(t, obj.Field2, obj2.Field2)
|
||||||
assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal)
|
assert.True(t, obj.Field3.Equal(obj2.Field3), "original: %v, unmarshalled: %v", obj, obj2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type testEmbedInterface struct {
|
|
||||||
Field1 string `json:"f1"`
|
|
||||||
Field2 int `json:"f2"`
|
|
||||||
Field3 Rational `json:"f3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmbeddedInterfaceSerializationGoWire(t *testing.T) {
|
|
||||||
r := testEmbedInterface{"foo", 10, NewRat(1, 3)}
|
|
||||||
|
|
||||||
rMarshal, err := ratCdc.MarshalJSON(r)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
var rUnmarshal testEmbedInterface
|
|
||||||
err = ratCdc.UnmarshalJSON(rMarshal, &rUnmarshal)
|
|
||||||
require.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, r.Field1, rUnmarshal.Field1)
|
|
||||||
assert.Equal(t, r.Field2, rUnmarshal.Field2)
|
|
||||||
assert.True(t, r.Field3.Equal(rUnmarshal.Field3), "original: %v, unmarshalled: %v", r, rUnmarshal)
|
|
||||||
}
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ func (m Mapper) removeDelegatorBond(delegator sdk.Address, candidateAddr sdk.Add
|
||||||
addrs = append(addrs[:i], addrs[i+1:]...)
|
addrs = append(addrs[:i], addrs[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
b, err := m.cdc.MarshalJSON(pks)
|
b, err := m.cdc.MarshalJSON(addrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
dbm "github.com/tendermint/tmlibs/db"
|
dbm "github.com/tendermint/tmlibs/db"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,18 +22,18 @@ func subspace(prefix []byte) (start, end []byte) {
|
||||||
return prefix, end
|
return prefix, end
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestInput(t *testing.T, isCheckTx bool) (store sdk.KVStore, ctx sdk.Context, key sdk.StoreKey) {
|
func createTestInput(t *testing.T, isCheckTx bool) (sdk.KVStore, sdk.Context, sdk.StoreKey) {
|
||||||
db := dbm.NewMemDB()
|
db := dbm.NewMemDB()
|
||||||
key = sdk.NewKVStoreKey("stake")
|
key := sdk.NewKVStoreKey("stake")
|
||||||
|
|
||||||
ms := store.NewCommitMultiStore(db)
|
ms := store.NewCommitMultiStore(db)
|
||||||
ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
ms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db)
|
||||||
err := ms.LoadLatestVersion()
|
err := ms.LoadLatestVersion()
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
ctx = sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil)
|
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, nil)
|
||||||
store = ms.GetKVStore(key)
|
store := ms.GetKVStore(key)
|
||||||
return
|
return store, ctx, key
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAddrs(n int) (addrs []crypto.Address) {
|
func newAddrs(n int) (addrs []crypto.Address) {
|
||||||
|
@ -50,7 +51,7 @@ func newPubKey(pk string) (res crypto.PubKey) {
|
||||||
//res, err = crypto.PubKeyFromBytes(pkBytes)
|
//res, err = crypto.PubKeyFromBytes(pkBytes)
|
||||||
var pkEd crypto.PubKeyEd25519
|
var pkEd crypto.PubKeyEd25519
|
||||||
copy(pkEd[:], pkBytes[:])
|
copy(pkEd[:], pkBytes[:])
|
||||||
return pkEd
|
return pkEd.Wrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// dummy pubkeys used for testing
|
// dummy pubkeys used for testing
|
||||||
|
@ -74,7 +75,7 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6
|
||||||
c := &Candidate{
|
c := &Candidate{
|
||||||
Status: Unbonded,
|
Status: Unbonded,
|
||||||
PubKey: pks[i],
|
PubKey: pks[i],
|
||||||
Owner: addrs[i],
|
Address: addrs[i],
|
||||||
Assets: sdk.NewRat(amts[i]),
|
Assets: sdk.NewRat(amts[i]),
|
||||||
Liabilities: sdk.NewRat(amts[i]),
|
Liabilities: sdk.NewRat(amts[i]),
|
||||||
VotingPower: sdk.NewRat(amts[i]),
|
VotingPower: sdk.NewRat(amts[i]),
|
||||||
|
@ -83,12 +84,14 @@ func candidatesFromActors(store sdk.KVStore, addrs []crypto.Address, amts []int6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveCandidate(store sdk.KVStore, c *Candidate) {} // TODO
|
||||||
|
|
||||||
func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) {
|
func candidatesFromActorsEmpty(addrs []crypto.Address) (candidates Candidates) {
|
||||||
for i := 0; i < len(addrs); i++ {
|
for i := 0; i < len(addrs); i++ {
|
||||||
c := &Candidate{
|
c := &Candidate{
|
||||||
Status: Unbonded,
|
Status: Unbonded,
|
||||||
PubKey: pks[i],
|
PubKey: pks[i],
|
||||||
Owner: addrs[i],
|
Address: addrs[i],
|
||||||
Assets: sdk.ZeroRat,
|
Assets: sdk.ZeroRat,
|
||||||
Liabilities: sdk.ZeroRat,
|
Liabilities: sdk.ZeroRat,
|
||||||
VotingPower: sdk.ZeroRat,
|
VotingPower: sdk.ZeroRat,
|
||||||
|
|
|
@ -22,6 +22,7 @@ func Tick(ctx sdk.Context, m Mapper) (change []*abci.Validator, err error) {
|
||||||
|
|
||||||
newVals := m.getValidators(params.MaxVals)
|
newVals := m.getValidators(params.MaxVals)
|
||||||
// XXX determine change from old validators, set to change
|
// XXX determine change from old validators, set to change
|
||||||
|
_ = newVals
|
||||||
return change, nil
|
return change, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,10 +167,10 @@ type MsgUnbond struct {
|
||||||
Shares string `json:"shares"`
|
Shares string `json:"shares"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMsgUnbond(shares string, address sdk.Address) MsgDelegate {
|
func NewMsgUnbond(bond sdk.Coin, address sdk.Address) MsgDelegate {
|
||||||
return MsgDelegate{
|
return MsgDelegate{
|
||||||
MsgAddr: NewMsgAddr(address),
|
MsgAddr: NewMsgAddr(address),
|
||||||
Shares: shares,
|
Bond: bond, // Shares: shares,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,10 +202,10 @@ func (msg MsgUnbond) ValidateBasic() sdk.Error {
|
||||||
func validateCoin(coin sdk.Coin) sdk.Error {
|
func validateCoin(coin sdk.Coin) sdk.Error {
|
||||||
coins := sdk.Coins{coin}
|
coins := sdk.Coins{coin}
|
||||||
if !coins.IsValid() {
|
if !coins.IsValid() {
|
||||||
return sdk.ErrInvalidCoins()
|
return sdk.ErrInvalidCoins(coins)
|
||||||
}
|
}
|
||||||
if !coins.IsPositive() {
|
if !coins.IsPositive() {
|
||||||
return fmt.Errorf("Amount must be > 0")
|
return sdk.ErrInvalidCoins(coins) // XXX: add "Amount must be > 0" ?
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ package stake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
abci "github.com/tendermint/abci/types"
|
|
||||||
crypto "github.com/tendermint/go-crypto"
|
crypto "github.com/tendermint/go-crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -167,7 +166,7 @@ type Description struct {
|
||||||
func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate {
|
func NewCandidate(pubKey crypto.PubKey, address sdk.Address, description Description) *Candidate {
|
||||||
return &Candidate{
|
return &Candidate{
|
||||||
Status: Unbonded,
|
Status: Unbonded,
|
||||||
PubKey: pubKet,
|
PubKey: pubKey,
|
||||||
Address: address,
|
Address: address,
|
||||||
Assets: sdk.ZeroRat,
|
Assets: sdk.ZeroRat,
|
||||||
Liabilities: sdk.ZeroRat,
|
Liabilities: sdk.ZeroRat,
|
||||||
|
@ -222,7 +221,7 @@ func (c *Candidate) removeShares(shares sdk.Rat, gs *GlobalState) (createdCoins
|
||||||
// Should only be called when the Candidate qualifies as a validator.
|
// Should only be called when the Candidate qualifies as a validator.
|
||||||
func (c *Candidate) validator() Validator {
|
func (c *Candidate) validator() Validator {
|
||||||
return Validator{
|
return Validator{
|
||||||
PubKey: c.PubKey,
|
Address: c.Address, // XXX !!!
|
||||||
VotingPower: c.VotingPower,
|
VotingPower: c.VotingPower,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,8 +233,9 @@ type Validator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ABCIValidator - Get the validator from a bond value
|
// ABCIValidator - Get the validator from a bond value
|
||||||
|
/* TODO
|
||||||
func (v Validator) ABCIValidator() (*abci.Validator, error) {
|
func (v Validator) ABCIValidator() (*abci.Validator, error) {
|
||||||
pkBytes, err := cdc.MarshalBinary(v.PubKey)
|
pkBytes, err := wire.MarshalBinary(v.PubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -244,6 +244,7 @@ func (v Validator) ABCIValidator() (*abci.Validator, error) {
|
||||||
Power: v.VotingPower.Evaluate(),
|
Power: v.VotingPower.Evaluate(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
//_________________________________________________________________________
|
//_________________________________________________________________________
|
||||||
|
|
||||||
|
@ -263,11 +264,11 @@ type DelegatorBond struct {
|
||||||
// Perform all the actions required to bond tokens to a delegator bond from their account
|
// Perform all the actions required to bond tokens to a delegator bond from their account
|
||||||
func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error {
|
func (bond *DelegatorBond) BondCoins(candidate *Candidate, tokens sdk.Coin, tr transact) sdk.Error {
|
||||||
|
|
||||||
_, err := tr.coinKeeper.SubtractCoins(tr.ctx, d.Address, sdk.Coins{tokens})
|
_, err := tr.coinKeeper.SubtractCoins(tr.ctx, candidate.Address, sdk.Coins{tokens})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newShares = candidate.addTokens(tokens.Amount, tr.gs)
|
newShares := candidate.addTokens(tokens.Amount, tr.gs)
|
||||||
bond.Shares = bond.Shares.Add(newShares)
|
bond.Shares = bond.Shares.Add(newShares)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -277,14 +278,14 @@ func (bond *DelegatorBond) UnbondCoins(candidate *Candidate, shares int64, tr tr
|
||||||
|
|
||||||
// subtract bond tokens from delegator bond
|
// subtract bond tokens from delegator bond
|
||||||
if bond.Shares.LT(shares) {
|
if bond.Shares.LT(shares) {
|
||||||
return ErrInsufficientFunds()
|
return sdk.ErrInsufficientFunds("") // TODO
|
||||||
}
|
}
|
||||||
bond.Shares = bond.Shares.Sub(shares)
|
bond.Shares = bond.Shares.Sub(shares)
|
||||||
|
|
||||||
returnAmount := candidate.removeShares(shares, tr.gs)
|
returnAmount := candidate.removeShares(shares, tr.gs)
|
||||||
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
|
returnCoins := sdk.Coins{{tr.params.BondDenom, returnAmount}}
|
||||||
|
|
||||||
_, err := tr.coinKeeper.AddCoins(tr.ctx, d.Address, returnCoins)
|
_, err := tr.coinKeeper.AddCoins(tr.ctx, candidate.Address, returnCoins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue