terra/token.go

265 lines
6.5 KiB
Go

package terra
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/galacticship/terra/cosmos"
"github.com/galacticship/terra/terra"
"github.com/pkg/errors"
"github.com/shopspring/decimal"
)
type tokenInfo struct {
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals uint8 `json:"decimals"`
TotalSupply decimal.Decimal `json:"total_supply"`
}
type Token interface {
Id() string
Address() cosmos.AccAddress
Symbol() string
Decimals() uint8
Balance(context.Context, *Querier, cosmos.AccAddress) (decimal.Decimal, error)
IsNative() bool
ValueFromTerra(value decimal.Decimal) decimal.Decimal
ValueToTerra(value decimal.Decimal) decimal.Decimal
NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error)
Equals(Token) bool
String() string
}
type Cw20Token struct {
address cosmos.AccAddress
symbol string
decimals uint8
}
func NewCw20Token(contractAddress string, symbol string, decimals uint8) (Cw20Token, error) {
accaddress, err := cosmos.AccAddressFromBech32(contractAddress)
if err != nil {
return Cw20Token{}, errors.Wrap(err, "validating token contract address")
}
return Cw20Token{
address: accaddress,
symbol: symbol,
decimals: decimals,
}, nil
}
func Cw20TokenFromAddress(ctx context.Context, querier *Querier, contractAddress string) (Cw20Token, error) {
if t, ok := Cw20TokenMap[contractAddress]; ok {
return t, nil
}
accaddress, err := cosmos.AccAddressFromBech32(contractAddress)
if err != nil {
return Cw20Token{}, errors.Wrap(err, "validating token contract address")
}
t := Cw20Token{
address: accaddress,
}
ti, err := t.getTokenInfo(ctx, querier)
if err != nil {
return Cw20Token{}, errors.Wrap(err, "getting token info")
}
t.symbol = ti.Symbol
t.decimals = ti.Decimals
return t, nil
}
func (t Cw20Token) getTokenInfo(ctx context.Context, querier *Querier) (tokenInfo, error) {
var ti tokenInfo
query := struct {
TokenInfo struct{} `json:"token_info"`
}{}
contract, err := NewContract(querier, t.address.String())
if err != nil {
return tokenInfo{}, errors.Wrap(err, "creating contract object")
}
err = contract.QueryStore(ctx, query, &ti)
if err != nil {
return tokenInfo{}, errors.Wrap(err, "calling token_info contract method")
}
return ti, nil
}
func (t Cw20Token) Id() string {
return t.Address().String()
}
func (t Cw20Token) Decimals() uint8 {
return t.decimals
}
func (t Cw20Token) DecimalsAsInt32() int32 {
return int32(t.decimals)
}
func (t Cw20Token) Symbol() string {
return t.symbol
}
func (t Cw20Token) Address() cosmos.AccAddress {
return t.address
}
func (t Cw20Token) ValueFromTerra(value decimal.Decimal) decimal.Decimal {
return value.Shift(-t.DecimalsAsInt32())
}
func (t Cw20Token) ValueToTerra(value decimal.Decimal) decimal.Decimal {
return value.Shift(t.DecimalsAsInt32())
}
func (t Cw20Token) Balance(ctx context.Context, querier *Querier, address cosmos.AccAddress) (decimal.Decimal, error) {
type query struct {
Balance struct {
Address string `json:"address"`
} `json:"balance"`
}
type response struct {
Balance decimal.Decimal `json:"balance"`
}
var q query
q.Balance.Address = address.String()
var r response
contract, err := NewContract(querier, t.address.String())
if err != nil {
return decimal.Zero, errors.Wrap(err, "creating contract object")
}
err = contract.QueryStore(ctx, q, &r)
if err != nil {
return decimal.Zero, errors.Wrap(err, "querying contract store")
}
return t.ValueFromTerra(r.Balance), nil
}
func (t Cw20Token) IsNative() bool {
return false
}
func (t Cw20Token) Equals(token Token) bool {
if !token.IsNative() && t.symbol == token.Symbol() {
return true
}
return false
}
func (t Cw20Token) String() string {
return t.symbol
}
func (t Cw20Token) NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error) {
jsonexecmsg, err := json.Marshal(execMsg)
if err != nil {
return nil, errors.Wrap(err, "marshalling execMsg")
}
type query struct {
Send struct {
Contract string `json:"contract"`
Amount decimal.Decimal `json:"amount"`
Message interface{} `json:"msg"`
} `json:"send"`
}
var q query
q.Send.Contract = contract.Address().String()
q.Send.Amount = t.ValueToTerra(amount)
q.Send.Message = jsonexecmsg
return terra.NewMsgExecuteContract(sender, t.Address(), q, nil)
}
type NativeToken struct {
symbol string
denom string
}
func NewNativeToken(symbol string, denom string) NativeToken {
return NativeToken{
denom: denom,
symbol: symbol,
}
}
func NativeTokenFromDenom(denom string) NativeToken {
if t, ok := NativeTokenMap[denom]; ok {
return t
}
return NewNativeToken("", denom)
}
func (n NativeToken) Id() string {
return n.denom
}
func (n NativeToken) Address() cosmos.AccAddress {
return cosmos.AccAddress{}
}
func (n NativeToken) Symbol() string {
return n.symbol
}
func (n NativeToken) Denom() string {
return n.denom
}
func (n NativeToken) Decimals() uint8 {
return 6
}
func (n NativeToken) Balance(ctx context.Context, querier *Querier, address cosmos.AccAddress) (decimal.Decimal, error) {
var response struct {
Balance struct {
Denom string `json:"denom"`
Amount decimal.Decimal `json:"amount"`
} `json:"balance"`
}
params := url.Values{}
params.Set("denom", n.Denom())
err := querier.GET(ctx, fmt.Sprintf("cosmos/bank/v1beta1/balances/%s/by_denom", address.String()), params, &response)
if err != nil {
return decimal.Zero, errors.Wrap(err, "executing get request")
}
return n.ValueFromTerra(response.Balance.Amount), nil
}
func (n NativeToken) DecimalsAsInt32() int32 {
return int32(n.Decimals())
}
func (n NativeToken) ValueFromTerra(value decimal.Decimal) decimal.Decimal {
return value.Shift(-n.DecimalsAsInt32())
}
func (n NativeToken) ValueToTerra(value decimal.Decimal) decimal.Decimal {
return value.Shift(n.DecimalsAsInt32())
}
func (n NativeToken) IsNative() bool {
return true
}
func (n NativeToken) Equals(token Token) bool {
if token.IsNative() && n.symbol == token.Symbol() {
return true
}
return false
}
func (n NativeToken) String() string {
return n.symbol
}
func (n NativeToken) NewMsgSendExecute(sender cosmos.AccAddress, contract *Contract, amount decimal.Decimal, execMsg interface{}) (cosmos.Msg, error) {
return terra.NewMsgExecuteContract(sender, contract.contractAddress, execMsg, cosmos.NewCoins(cosmos.NewInt64Coin(n.Id(), n.ValueToTerra(amount).IntPart())))
}