diff --git a/client/transaction/time_lock.go b/client/transaction/time_lock.go new file mode 100644 index 00000000..bbfce5f0 --- /dev/null +++ b/client/transaction/time_lock.go @@ -0,0 +1,90 @@ +package transaction + +import ( + "strconv" + + "github.com/binance-chain/go-sdk/common/types" + "github.com/binance-chain/go-sdk/types/msg" + "github.com/binance-chain/go-sdk/types/tx" +) + +type TimeLockResult struct { + tx.TxCommitResult + LockId int64 `json:"lock_id"` +} + +func (c *client) TimeLock(description string, amount types.Coins, lockTime int64, sync bool, options ...Option) (*TimeLockResult, error) { + fromAddr := c.keyManager.GetAddr() + + lockMsg := msg.NewTimeLockMsg(fromAddr, description, amount, lockTime) + err := lockMsg.ValidateBasic() + if err != nil { + return nil, err + } + commit, err := c.broadcastMsg(lockMsg, sync, options...) + if err != nil { + return nil, err + } + var lockId int64 + if commit.Ok && sync { + lockId, err = strconv.ParseInt(string(commit.Data), 10, 64) + if err != nil { + return nil, err + } + } + return &TimeLockResult{*commit, lockId}, err +} + +type TimeUnLockResult struct { + tx.TxCommitResult + LockId int64 `json:"lock_id"` +} + +func (c *client) TimeUnLock(id int64, sync bool, options ...Option) (*TimeUnLockResult, error) { + fromAddr := c.keyManager.GetAddr() + + unlockMsg := msg.NewTimeUnlockMsg(fromAddr, id) + err := unlockMsg.ValidateBasic() + if err != nil { + return nil, err + } + commit, err := c.broadcastMsg(unlockMsg, sync, options...) + if err != nil { + return nil, err + } + var lockId int64 + if commit.Ok && sync { + lockId, err = strconv.ParseInt(string(commit.Data), 10, 64) + if err != nil { + return nil, err + } + } + return &TimeUnLockResult{*commit, lockId}, err +} + +type TimeReLockResult struct { + tx.TxCommitResult + LockId int64 `json:"lock_id"` +} + +func (c *client) TimeReLock(id int64, description string, amount types.Coins, lockTime int64, sync bool, options ...Option) (*TimeReLockResult, error) { + fromAddr := c.keyManager.GetAddr() + + relockMsg := msg.NewTimeRelockMsg(fromAddr, id, description, amount, lockTime) + err := relockMsg.ValidateBasic() + if err != nil { + return nil, err + } + commit, err := c.broadcastMsg(relockMsg, sync, options...) + if err != nil { + return nil, err + } + var lockId int64 + if commit.Ok && sync { + lockId, err = strconv.ParseInt(string(commit.Data), 10, 64) + if err != nil { + return nil, err + } + } + return &TimeReLockResult{*commit, lockId}, err +} diff --git a/client/transaction/transaction.go b/client/transaction/transaction.go index e1b67b4b..8444e664 100644 --- a/client/transaction/transaction.go +++ b/client/transaction/transaction.go @@ -6,6 +6,7 @@ import ( "github.com/binance-chain/go-sdk/client/basic" "github.com/binance-chain/go-sdk/client/query" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" @@ -21,6 +22,9 @@ type TransactionClient interface { IssueToken(name, symbol string, supply int64, sync bool, mintable bool, options ...Option) (*IssueTokenResult, error) SendToken(transfers []msg.Transfer, sync bool, options ...Option) (*SendTokenResult, error) MintToken(symbol string, amount int64, sync bool, options ...Option) (*MintTokenResult, error) + TimeLock(description string, amount types.Coins, lockTime int64, sync bool, options ...Option) (*TimeLockResult, error) + TimeUnLock(id int64, sync bool, options ...Option) (*TimeUnLockResult, error) + TimeReLock(id int64, description string, amount types.Coins, lockTime int64, sync bool, options ...Option) (*TimeReLockResult, error) SubmitListPairProposal(title string, param msg.ListTradingPairParams, initialDeposit int64, votingPeriod time.Duration, sync bool, options ...Option) (*SubmitProposalResult, error) SubmitProposal(title string, description string, proposalType msg.ProposalKind, initialDeposit int64, votingPeriod time.Duration, sync bool, options ...Option) (*SubmitProposalResult, error) diff --git a/common/types/address.go b/common/types/address.go index 73ac0547..8ced3791 100644 --- a/common/types/address.go +++ b/common/types/address.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + "github.com/tendermint/tendermint/crypto" "github.com/binance-chain/go-sdk/common/bech32" ) @@ -16,6 +17,11 @@ type AccAddress []byte type ChainNetwork uint8 const ( + AddrLen = 20 + + bech32PrefixConsPub = "bcap" + bech32PrefixConsAddr = "bca" + TestNetwork ChainNetwork = iota ProdNetwork ) @@ -119,3 +125,18 @@ func (bz AccAddress) String() string { } return bech32Addr } + +func MustBech32ifyConsPub(pub crypto.PubKey) string { + enc, err := Bech32ifyConsPub(pub) + if err != nil { + panic(err) + } + + return enc +} + +// Bech32ifyConsPub returns a Bech32 encoded string containing the +// Bech32PrefixConsPub prefixfor a given consensus node's PubKey. +func Bech32ifyConsPub(pub crypto.PubKey) (string, error) { + return bech32.ConvertAndEncode(bech32PrefixConsPub, pub.Bytes()) +} diff --git a/common/types/coins.go b/common/types/coins.go index f4007c8b..d5a686aa 100644 --- a/common/types/coins.go +++ b/common/types/coins.go @@ -113,6 +113,15 @@ func (coins Coins) IsEqual(coinsB Coins) bool { return true } +func (coins Coins) IsZero() bool { + for _, coin := range coins { + if !coin.IsZero() { + return false + } + } + return true +} + func (coins Coins) IsNotNegative() bool { if len(coins) == 0 { return true diff --git a/common/types/dec.go b/common/types/dec.go index 6f400762..fd1b2623 100644 --- a/common/types/dec.go +++ b/common/types/dec.go @@ -3,9 +3,30 @@ package types import ( "encoding/json" "fmt" + "math/big" "strconv" ) +// number of decimal places +const ( + Precision = 8 + + // bytes required to represent the above precision + // ceil(log2(9999999999)) + DecimalPrecisionBits = 34 +) + +var ( + precisionReuse = new(big.Int).Exp(big.NewInt(10), big.NewInt(Precision), nil).Int64() + zeroInt = big.NewInt(0) + oneInt = big.NewInt(1) + tenInt = big.NewInt(10) +) + +func precisionInt() int64 { + return precisionReuse +} + type Dec struct { int64 `json:"int"` } @@ -61,3 +82,32 @@ func NewDecFromStr(str string) (d Dec, err error) { } return Dec{value}, nil } + +//nolint +func (d Dec) IsNil() bool { return false } // is decimal nil +func (d Dec) IsZero() bool { return d.int64 == 0 } // is equal to zero +func (d Dec) Equal(d2 Dec) bool { return d.int64 == d2.int64 } // equal decimals +func (d Dec) GT(d2 Dec) bool { return d.int64 > d2.int64 } // greater than +func (d Dec) GTE(d2 Dec) bool { return d.int64 >= d2.int64 } // greater than or equal +func (d Dec) LT(d2 Dec) bool { return d.int64 < d2.int64 } // less than +func (d Dec) LTE(d2 Dec) bool { return d.int64 <= d2.int64 } // less than or equal +func (d Dec) Neg() Dec { return Dec{-d.int64} } // reverse the decimal sign +func (d Dec) Abs() Dec { + if d.int64 < 0 { + return d.Neg() + } + return d +} + +// subtraction +func (d Dec) Sub(d2 Dec) Dec { + c := d.int64 - d2.int64 + if (c < d.int64) != (d2.int64 > 0) { + panic("Int overflow") + } + return Dec{c} +} + +// nolint - common values +func ZeroDec() Dec { return Dec{0} } +func OneDec() Dec { return Dec{precisionInt()} } diff --git a/common/types/proposal.go b/common/types/proposal.go index 48ac4d3c..108a6b5e 100644 --- a/common/types/proposal.go +++ b/common/types/proposal.go @@ -100,7 +100,6 @@ func (pt ProposalKind) String() string { } } - type ProposalStatus byte //nolint diff --git a/common/types/stake.go b/common/types/stake.go index fe86ff06..6a14abac 100644 --- a/common/types/stake.go +++ b/common/types/stake.go @@ -1,11 +1,15 @@ package types import ( + "bytes" + "encoding/hex" "encoding/json" + "errors" "fmt" "time" "github.com/binance-chain/go-sdk/common/bech32" + "github.com/tendermint/tendermint/crypto" ) type ValAddress []byte @@ -19,19 +23,87 @@ const ( Bonded BondStatus = 0x02 ) -// Description - description fields for a validator -type Description struct { - Moniker string `json:"moniker"` // name - Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) - Website string `json:"website"` // optional website link - Details string `json:"details"` // optional details +type ( + // Commission defines a commission parameters for a given validator. + Commission struct { + Rate Dec `json:"rate"` // the commission rate charged to delegators + MaxRate Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxChangeRate Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + UpdateTime time.Time `json:"update_time"` // the last time the commission rate was changed + } + + // CommissionMsg defines a commission message to be used for creating a + // validator. + CommissionMsg struct { + Rate Dec `json:"rate"` // the commission rate charged to delegators + MaxRate Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxChangeRate Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + } +) + +func NewCommission(rate, maxRate, maxChangeRate Dec) Commission { + return Commission{ + Rate: rate, + MaxRate: maxRate, + MaxChangeRate: maxChangeRate, + UpdateTime: time.Unix(0, 0).UTC(), + } } -type Commission struct { - Rate Dec `json:"rate"` // the commission rate charged to delegators - MaxRate Dec `json:"max_rate"` // maximum commission rate which validator can ever charge - MaxChangeRate Dec `json:"max_change_rate"` // maximum daily increase of the validator commission - UpdateTime time.Time `json:"update_time"` // the last time the commission rate was changed +// Validate performs basic sanity validation checks of initial commission +// parameters. If validation fails, an error is returned. +func (c Commission) Validate() error { + switch { + case c.MaxRate.LT(ZeroDec()): + // max rate cannot be negative + return fmt.Errorf("Commission maxrate %v is negative", c.MaxRate) + + case c.MaxRate.GT(OneDec()): + // max rate cannot be greater than 100% + return fmt.Errorf("Commission maxrate %v can't be greater than 100%", c.MaxRate) + + case c.Rate.LT(ZeroDec()): + // rate cannot be negative + return fmt.Errorf("Commission rate %v can't be negative ", c.Rate) + + case c.Rate.GT(c.MaxRate): + // rate cannot be greater than the max rate + return fmt.Errorf("Commission rate %v can't be greater than maxrate %v", c.Rate, c.MaxRate) + + case c.MaxChangeRate.LT(ZeroDec()): + // change rate cannot be negative + return fmt.Errorf("Commission change rate %v can't be negative", c.MaxChangeRate) + + case c.MaxChangeRate.GT(c.MaxRate): + // change rate cannot be greater than the max rate + return fmt.Errorf("Commission change rate %v can't be greater than MaxRat %v", c.MaxChangeRate, c.MaxRate) + } + + return nil +} + +// ValidateNewRate performs basic sanity validation checks of a new commission +// rate. If validation fails, an SDK error is returned. +func (c Commission) ValidateNewRate(newRate Dec, blockTime time.Time) error { + switch { + case blockTime.Sub(c.UpdateTime).Hours() < 24: + // new rate cannot be changed more than once within 24 hours + return fmt.Errorf("new rate %v cannot be changed more than once within 24 hours", blockTime.Sub(c.UpdateTime).Hours()) + + case newRate.LT(ZeroDec()): + // new rate cannot be negative + return fmt.Errorf("new rate %v cannot be negative", newRate) + + case newRate.GT(c.MaxRate): + // new rate cannot be greater than the max rate + return fmt.Errorf("new rate %v cannot be greater than the max rate %v", newRate, c.MaxRate) + + case newRate.Sub(c.Rate).Abs().GT(c.MaxChangeRate): + // new rate % points change cannot be greater than the max change rate + return fmt.Errorf("new rate %v points change cannot be greater than the max change rate %v", newRate.Sub(c.Rate).Abs(), c.MaxChangeRate) + } + + return nil } func (c Commission) String() string { @@ -40,6 +112,14 @@ func (c Commission) String() string { ) } +// Description - description fields for a validator +type Description struct { + Moniker string `json:"moniker"` // name + Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) + Website string `json:"website"` // optional website link + Details string `json:"details"` // optional details +} + // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this @@ -122,3 +202,122 @@ func ValAddressFromBech32(address string) (addr ValAddress, err error) { return ValAddress(bz), nil } + +// consensus node +// ---------------------------------------------------------------------------- + +// ConsAddress defines a wrapper around bytes meant to present a consensus node. +// When marshaled to a string or JSON, it uses Bech32. +type ConsAddress []byte + +// ConsAddressFromHex creates a ConsAddress from a hex string. +func ConsAddressFromHex(address string) (addr ConsAddress, err error) { + if len(address) == 0 { + return addr, errors.New("decoding Bech32 address failed: must provide an address") + } + + bz, err := hex.DecodeString(address) + if err != nil { + return nil, err + } + + return ConsAddress(bz), nil +} + +// ConsAddressFromBech32 creates a ConsAddress from a Bech32 string. +func ConsAddressFromBech32(address string) (addr ConsAddress, err error) { + bz, err := GetFromBech32(address, bech32PrefixConsAddr) + if err != nil { + return nil, err + } + + return ConsAddress(bz), nil +} + +// get ConsAddress from pubkey +func GetConsAddress(pubkey crypto.PubKey) ConsAddress { + return ConsAddress(pubkey.Address()) +} + +// Returns boolean for whether two ConsAddress are Equal +func (ca ConsAddress) Equals(ca2 ConsAddress) bool { + if ca.Empty() && ca2.Empty() { + return true + } + + return bytes.Compare(ca.Bytes(), ca2.Bytes()) == 0 +} + +// Returns boolean for whether an ConsAddress is empty +func (ca ConsAddress) Empty() bool { + if ca == nil { + return true + } + + ca2 := ConsAddress{} + return bytes.Compare(ca.Bytes(), ca2.Bytes()) == 0 +} + +// Marshal returns the raw address bytes. It is needed for protobuf +// compatibility. +func (ca ConsAddress) Marshal() ([]byte, error) { + return ca, nil +} + +// Unmarshal sets the address to the given data. It is needed for protobuf +// compatibility. +func (ca *ConsAddress) Unmarshal(data []byte) error { + *ca = data + return nil +} + +// MarshalJSON marshals to JSON using Bech32. +func (ca ConsAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(ca.String()) +} + +// UnmarshalJSON unmarshals from JSON assuming Bech32 encoding. +func (ca *ConsAddress) UnmarshalJSON(data []byte) error { + var s string + + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + ca2, err := ConsAddressFromBech32(s) + if err != nil { + return err + } + + *ca = ca2 + return nil +} + +// Bytes returns the raw address bytes. +func (ca ConsAddress) Bytes() []byte { + return ca +} + +// String implements the Stringer interface. +func (ca ConsAddress) String() string { + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixConsAddr, ca.Bytes()) + if err != nil { + panic(err) + } + + return bech32Addr +} + +// Format implements the fmt.Formatter interface. +// nolint: errcheck +func (ca ConsAddress) Format(s fmt.State, verb rune) { + switch verb { + case 's': + s.Write([]byte(fmt.Sprintf("%s", ca.String()))) + case 'p': + s.Write([]byte(fmt.Sprintf("%p", ca))) + default: + s.Write([]byte(fmt.Sprintf("%X", []byte(ca)))) + } +} diff --git a/e2e/e2e_trans_test.go b/e2e/e2e_trans_test.go index d8da2e1b..ba0d36c7 100644 --- a/e2e/e2e_trans_test.go +++ b/e2e/e2e_trans_test.go @@ -83,6 +83,22 @@ func TestTransProcess(t *testing.T) { assert.NoError(t, err) fmt.Printf("Get time: %v \n", time) + //----- time lock ----------- + lockResult,err:=client.TimeLock("test lock",ctypes.Coins{{"BNB",100000000}},int64(time2.Now().Add(65*time2.Second).Unix()),true) + assert.NoError(t,err) + fmt.Printf("timelock %d",lockResult.LockId) + + //----- time relock --------- + relockResult,err:=client.TimeReLock(lockResult.LockId,"test lock",ctypes.Coins{{"BNB",200000000}},int64(time2.Now().Add(65*time2.Second).Unix()),true) + assert.NoError(t,err) + fmt.Printf("timelock %d",relockResult.LockId) + + //------ time unlock -------- + time2.Sleep(70*time2.Second) + unlockResult,err:= client.TimeUnLock(relockResult.LockId,true) + assert.NoError(t,err) + fmt.Printf("timelock %d",unlockResult.LockId) + //----- Create order ----------- createOrderResult, err := client.CreateOrder(tradeSymbol, nativeSymbol, msg.OrderSide.BUY, 100000000, 100000000, true, transaction.WithSource(100),transaction.WithMemo("test memo")) assert.NoError(t, err) diff --git a/types/msg/msg-timelock.go b/types/msg/msg-timelock.go new file mode 100644 index 00000000..94d1c0e4 --- /dev/null +++ b/types/msg/msg-timelock.go @@ -0,0 +1,180 @@ +package msg + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/binance-chain/go-sdk/common/types" + "github.com/tendermint/tendermint/crypto" +) + +const ( + MaxTimeLockDescriptionLength = 128 + MinLockTime = 60 * time.Second + + InitialRecordId = 1 +) + +var ( + TimeLockCoinsAccAddr = types.AccAddress(crypto.AddressHash([]byte("BinanceChainTimeLockCoins"))) +) + +type TimeLockMsg struct { + From types.AccAddress `json:"from"` + Description string `json:"description"` + Amount types.Coins `json:"amount"` + LockTime int64 `json:"lock_time"` +} + +func NewTimeLockMsg(from types.AccAddress, description string, amount types.Coins, lockTime int64) TimeLockMsg { + return TimeLockMsg{ + From: from, + Description: description, + Amount: amount, + LockTime: lockTime, + } +} + +func (msg TimeLockMsg) Route() string { return MsgRoute } +func (msg TimeLockMsg) Type() string { return "timeLock" } +func (msg TimeLockMsg) String() string { + return fmt.Sprintf("TimeLock{%s#%v#%v#%v}", msg.From, msg.Description, msg.Amount, msg.LockTime) +} +func (msg TimeLockMsg) GetInvolvedAddresses() []types.AccAddress { + return []types.AccAddress{msg.From, TimeLockCoinsAccAddr} +} +func (msg TimeLockMsg) GetSigners() []types.AccAddress { return []types.AccAddress{msg.From} } + +func (msg TimeLockMsg) ValidateBasic() error { + if len(msg.Description) == 0 || len(msg.Description) > MaxTimeLockDescriptionLength { + return fmt.Errorf("length of description(%d) should be larger than 0 and be less than or equal to %d", + len(msg.Description), MaxTimeLockDescriptionLength) + } + + if msg.LockTime <= 0 { + return fmt.Errorf("lock time(%d) should be larger than 0", msg.LockTime) + } + + if !msg.Amount.IsValid() { + return fmt.Errorf("amount %v is invalid", msg.Amount) + } + + if !msg.Amount.IsPositive() { + return fmt.Errorf("amount %v can't be negative", msg.Amount) + } + + return nil +} + +func (msg TimeLockMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +type TimeRelockMsg struct { + From types.AccAddress `json:"from"` + Id int64 `json:"time_lock_id"` + Description string `json:"description"` + Amount types.Coins `json:"amount"` + LockTime int64 `json:"lock_time"` +} + +func NewTimeRelockMsg(from types.AccAddress, id int64, description string, amount types.Coins, lockTime int64) TimeRelockMsg { + return TimeRelockMsg{ + From: from, + Id: id, + Description: description, + Amount: amount, + LockTime: lockTime, + } +} + +func (msg TimeRelockMsg) Route() string { return MsgRoute } +func (msg TimeRelockMsg) Type() string { return "timeRelock" } +func (msg TimeRelockMsg) String() string { + return fmt.Sprintf("TimeRelock{%v#%s#%v#%v#%v}", msg.Id, msg.From, msg.Description, msg.Amount, msg.LockTime) +} +func (msg TimeRelockMsg) GetInvolvedAddresses() []types.AccAddress { + return []types.AccAddress{msg.From, TimeLockCoinsAccAddr} +} +func (msg TimeRelockMsg) GetSigners() []types.AccAddress { return []types.AccAddress{msg.From} } + +func (msg TimeRelockMsg) ValidateBasic() error { + if msg.Id < InitialRecordId { + return fmt.Errorf("time lock id should not be less than %d", InitialRecordId) + } + + if len(msg.Description) > MaxTimeLockDescriptionLength { + return fmt.Errorf("length of description(%d) should be less than or equal to %d", + len(msg.Description), MaxTimeLockDescriptionLength) + } + + if msg.LockTime < 0 { + return fmt.Errorf("lock time(%d) should not be less than 0", msg.LockTime) + } + + if !msg.Amount.IsValid() { + return fmt.Errorf("amount %v is invalid", msg.Amount) + } + + if !msg.Amount.IsNotNegative() { + return fmt.Errorf("amount %v can't be negative", msg.Amount) + } + + if len(msg.Description) == 0 && + msg.Amount.IsZero() && + msg.LockTime == 0 { + return fmt.Errorf("nothing to update for time lock") + } + + return nil +} + +func (msg TimeRelockMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} + +type TimeUnlockMsg struct { + From types.AccAddress `json:"from"` + Id int64 `json:"time_lock_id"` +} + +func NewTimeUnlockMsg(from types.AccAddress, id int64) TimeUnlockMsg { + return TimeUnlockMsg{ + From: from, + Id: id, + } +} + +func (msg TimeUnlockMsg) Route() string { return MsgRoute } +func (msg TimeUnlockMsg) Type() string { return "timeUnlock" } +func (msg TimeUnlockMsg) String() string { + return fmt.Sprintf("TimeUnlock{%s#%v}", msg.From, msg.Id) +} +func (msg TimeUnlockMsg) GetInvolvedAddresses() []types.AccAddress { + return []types.AccAddress{msg.From, TimeLockCoinsAccAddr} +} +func (msg TimeUnlockMsg) GetSigners() []types.AccAddress { return []types.AccAddress{msg.From} } + +func (msg TimeUnlockMsg) ValidateBasic() error { + if msg.Id < InitialRecordId { + return fmt.Errorf("time lock id should not be less than %d", InitialRecordId) + } + return nil +} + +func (msg TimeUnlockMsg) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return b +} diff --git a/types/msg/msg-validator.go b/types/msg/msg-validator.go new file mode 100644 index 00000000..88ec5da4 --- /dev/null +++ b/types/msg/msg-validator.go @@ -0,0 +1,179 @@ +package msg + +import ( + "bytes" + "fmt" + + "github.com/binance-chain/go-sdk/common/types" + "github.com/tendermint/tendermint/crypto" +) + +// Description - description fields for a validator +type Description struct { + Moniker string `json:"moniker"` // name + Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) + Website string `json:"website"` // optional website link + Details string `json:"details"` // optional details +} + +// EnsureLength ensures the length of a validator's description. +func (d Description) EnsureLength() (Description, error) { + if len(d.Moniker) > 70 { + return d, fmt.Errorf("len moniker %d can be more than %d ", len(d.Moniker), 70) + } + if len(d.Identity) > 3000 { + return d, fmt.Errorf("len Identity %d can be more than %d ", len(d.Identity), 3000) + } + if len(d.Website) > 140 { + return d, fmt.Errorf("len Website %d can be more than %d ", len(d.Website), 140) + } + if len(d.Details) > 280 { + return d, fmt.Errorf("len Details %d can be more than %d ", len(d.Details), 280) + } + + return d, nil +} + +// MsgCreateValidator - struct for bonding transactions +type MsgCreateValidator struct { + Description Description + Commission types.CommissionMsg + DelegatorAddr types.AccAddress `json:"delegator_address"` + ValidatorAddr types.ValAddress `json:"validator_address"` + PubKey crypto.PubKey `json:"pubkey"` + Delegation types.Coin `json:"delegation"` +} + +type MsgCreateValidatorProposal struct { + MsgCreateValidator + ProposalId int64 `json:"proposal_id"` +} + +func (msg MsgCreateValidator) Route() string { return MsgRoute } +func (msg MsgCreateValidator) Type() string { return "create_validator" } + +// Return address(es) that must sign over msg.GetSignBytes() +func (msg MsgCreateValidator) GetSigners() []types.AccAddress { + // delegator is first signer so delegator pays fees + addrs := []types.AccAddress{msg.DelegatorAddr} + + if !bytes.Equal(msg.DelegatorAddr.Bytes(), msg.ValidatorAddr.Bytes()) { + // if validator addr is not same as delegator addr, validator must sign + // msg as well + addrs = append(addrs, types.AccAddress(msg.ValidatorAddr)) + } + return addrs +} + +// get the bytes for the message signer to sign on +func (msg MsgCreateValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + Description + DelegatorAddr types.AccAddress `json:"delegator_address"` + ValidatorAddr types.ValAddress `json:"validator_address"` + PubKey string `json:"pubkey"` + Delegation types.Coin `json:"delegation"` + }{ + Description: msg.Description, + ValidatorAddr: msg.ValidatorAddr, + PubKey: types.MustBech32ifyConsPub(msg.PubKey), + Delegation: msg.Delegation, + }) + if err != nil { + panic(err) + } + return MustSortJSON(b) +} + +func (msg MsgCreateValidator) GetInvolvedAddresses() []types.AccAddress { + return msg.GetSigners() +} + +// quick validity check +func (msg MsgCreateValidator) ValidateBasic() error { + if len(msg.DelegatorAddr) != types.AddrLen { + return fmt.Errorf("Expected delegator address length is %d, actual length is %d", types.AddrLen, len(msg.DelegatorAddr)) + } + if len(msg.ValidatorAddr) != types.AddrLen { + return fmt.Errorf("Expected validator address length is %d, actual length is %d", types.AddrLen, len(msg.ValidatorAddr)) + } + if !(msg.Delegation.Amount > 0) { + return fmt.Errorf("DelegationAmount %d is invalid", msg.Delegation.Amount) + } + if msg.Description == (Description{}) { + return fmt.Errorf("description must be included") + } + if _, err := msg.Description.EnsureLength(); err != nil { + return err + } + commission := types.NewCommission(msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate) + if err := commission.Validate(); err != nil { + return err + } + + return nil +} + +type MsgRemoveValidator struct { + LauncherAddr types.AccAddress `json:"launcher_addr"` + ValAddr types.ValAddress `json:"val_addr"` + ValConsAddr types.ConsAddress `json:"val_cons_addr"` + ProposalId int64 `json:"proposal_id"` +} + +func NewMsgRemoveValidator(launcherAddr types.AccAddress, valAddr types.ValAddress, + valConsAddr types.ConsAddress, proposalId int64) MsgRemoveValidator { + return MsgRemoveValidator{ + LauncherAddr: launcherAddr, + ValAddr: valAddr, + ValConsAddr: valConsAddr, + ProposalId: proposalId, + } +} + +//nolint +func (msg MsgRemoveValidator) Route() string { return MsgRoute } +func (msg MsgRemoveValidator) Type() string { return "remove_validator" } +func (msg MsgRemoveValidator) GetSigners() []types.AccAddress { + return []types.AccAddress{msg.LauncherAddr} +} + +// get the bytes for the message signer to sign on +func (msg MsgRemoveValidator) GetSignBytes() []byte { + b, err := MsgCdc.MarshalJSON(struct { + LauncherAddr types.AccAddress `json:"launcher_addr"` + ValAddr types.ValAddress `json:"val_addr"` + ValConsAddr types.ConsAddress `json:"val_cons_addr"` + ProposalId int64 `json:"proposal_id"` + }{ + LauncherAddr: msg.LauncherAddr, + ValAddr: msg.ValAddr, + ValConsAddr: msg.ValConsAddr, + ProposalId: msg.ProposalId, + }) + if err != nil { + panic(err) + } + return MustSortJSON(b) +} + +// quick validity check +func (msg MsgRemoveValidator) ValidateBasic() error { + if len(msg.LauncherAddr) != types.AddrLen { + return fmt.Errorf("Expected launcher address length is %d, actual length is %d", types.AddrLen, len(msg.LauncherAddr)) + } + if len(msg.ValAddr) != types.AddrLen { + return fmt.Errorf("Expected validator address length is %d, actual length is %d", types.AddrLen, len(msg.ValAddr)) + } + if len(msg.ValConsAddr) != types.AddrLen { + return fmt.Errorf("Expected validator consensus address length is %d, actual length is %d", types.AddrLen, len(msg.ValConsAddr)) + } + if msg.ProposalId <= 0 { + return fmt.Errorf("Proposal id is expected to be positive, actual value is %d", msg.ProposalId) + } + return nil +} + +func (msg MsgRemoveValidator) GetInvolvedAddresses() []types.AccAddress { + return []types.AccAddress{msg.LauncherAddr} +} diff --git a/types/msg/wire.go b/types/msg/wire.go index b0f3d1c0..f0c6fa16 100644 --- a/types/msg/wire.go +++ b/types/msg/wire.go @@ -14,8 +14,13 @@ func RegisterCodec(cdc *amino.Codec) { cdc.RegisterConcrete(CancelOrderMsg{}, "dex/CancelOrder", nil) cdc.RegisterConcrete(TokenIssueMsg{}, "tokens/IssueMsg", nil) cdc.RegisterConcrete(TokenBurnMsg{}, "tokens/BurnMsg", nil) + + cdc.RegisterConcrete(TimeLockMsg{}, "tokens/TimeLockMsg", nil) cdc.RegisterConcrete(TokenFreezeMsg{}, "tokens/FreezeMsg", nil) cdc.RegisterConcrete(TokenUnfreezeMsg{}, "tokens/UnfreezeMsg", nil) + + cdc.RegisterConcrete(TimeUnlockMsg{}, "tokens/TimeUnlockMsg", nil) + cdc.RegisterConcrete(TimeRelockMsg{}, "tokens/TimeRelockMsg", nil) cdc.RegisterConcrete(DexListMsg{}, "dex/ListMsg", nil) cdc.RegisterConcrete(MintMsg{}, "tokens/MintMsg", nil) //Must use cosmos-sdk. @@ -24,6 +29,10 @@ func RegisterCodec(cdc *amino.Codec) { cdc.RegisterConcrete(SubmitProposalMsg{}, "cosmos-sdk/MsgSubmitProposal", nil) cdc.RegisterConcrete(DepositMsg{}, "cosmos-sdk/MsgDeposit", nil) cdc.RegisterConcrete(VoteMsg{}, "cosmos-sdk/MsgVote", nil) + + cdc.RegisterConcrete(MsgCreateValidator{}, "cosmos-sdk/MsgCreateValidator", nil) + cdc.RegisterConcrete(MsgRemoveValidator{}, "cosmos-sdk/MsgRemoveValidator", nil) + cdc.RegisterConcrete(MsgCreateValidatorProposal{}, "cosmos-sdk/MsgCreateValidatorProposal", nil) } func init() {