mirror of
https://github.com/galacticship/terra.git
synced 2024-11-09 01:37:51 +00:00
311 lines
9.3 KiB
Go
311 lines
9.3 KiB
Go
|
package terra
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/galacticship/terra/cosmos"
|
||
|
"github.com/pkg/errors"
|
||
|
"github.com/shopspring/decimal"
|
||
|
)
|
||
|
|
||
|
type Pair interface {
|
||
|
CommissionRate() decimal.Decimal
|
||
|
LpToken() Cw20Token
|
||
|
Token1() Token
|
||
|
Token2() Token
|
||
|
ContractAddressString() string
|
||
|
ContractAddress() cosmos.AccAddress
|
||
|
|
||
|
PoolInfo(ctx context.Context) (PoolInfo, error)
|
||
|
NewWithdrawLiquidityMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error)
|
||
|
Share(ctx context.Context, lpAmount decimal.Decimal) (token1Amount decimal.Decimal, token2Amount decimal.Decimal, err error)
|
||
|
NewSimpleSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal) (cosmos.Msg, error)
|
||
|
NewSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal, spread string, beliefPrice decimal.Decimal) (cosmos.Msg, error)
|
||
|
SimulateSwap(ctx context.Context, offer Token, amount decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error)
|
||
|
Equals(pair Pair) bool
|
||
|
String() string
|
||
|
}
|
||
|
|
||
|
type BasePair struct {
|
||
|
*Contract
|
||
|
|
||
|
token1 Token
|
||
|
token2 Token
|
||
|
lpToken Cw20Token
|
||
|
commissionRate decimal.Decimal
|
||
|
|
||
|
aiFactory AssetInfoFactory
|
||
|
}
|
||
|
|
||
|
func NewBasePair(querier *Querier, contractAddress string, token1 Token, token2 Token, lpToken Cw20Token, commissionRate decimal.Decimal, aiFactory AssetInfoFactory) (*BasePair, error) {
|
||
|
contract, err := NewContract(querier, contractAddress)
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "init contract object")
|
||
|
}
|
||
|
p := &BasePair{
|
||
|
Contract: contract,
|
||
|
commissionRate: commissionRate,
|
||
|
aiFactory: aiFactory,
|
||
|
token1: token1,
|
||
|
token2: token2,
|
||
|
lpToken: lpToken,
|
||
|
}
|
||
|
return p, nil
|
||
|
}
|
||
|
|
||
|
func (p BasePair) CommissionRate() decimal.Decimal {
|
||
|
return p.commissionRate
|
||
|
}
|
||
|
|
||
|
func (p BasePair) LpToken() Cw20Token {
|
||
|
return p.lpToken
|
||
|
}
|
||
|
|
||
|
func (p BasePair) Token1() Token {
|
||
|
return p.token1
|
||
|
}
|
||
|
|
||
|
func (p BasePair) Token2() Token {
|
||
|
return p.token2
|
||
|
}
|
||
|
|
||
|
func (p BasePair) ContractAddressString() string {
|
||
|
return p.contractAddress.String()
|
||
|
}
|
||
|
func (p BasePair) ContractAddress() cosmos.AccAddress {
|
||
|
return p.contractAddress
|
||
|
}
|
||
|
|
||
|
func (p *BasePair) SetToken1(token Token) {
|
||
|
p.token1 = token
|
||
|
}
|
||
|
func (p *BasePair) SetToken2(token Token) {
|
||
|
p.token2 = token
|
||
|
}
|
||
|
func (p *BasePair) SetLpToken(token Cw20Token) {
|
||
|
p.lpToken = token
|
||
|
}
|
||
|
|
||
|
func (p *BasePair) Config(ctx context.Context) (tokens []Token, lpToken Token, err error) {
|
||
|
var q struct {
|
||
|
Pair struct {
|
||
|
} `json:"pair"`
|
||
|
}
|
||
|
type response struct {
|
||
|
AssetInfos []json.RawMessage `json:"asset_infos"`
|
||
|
ContractAddr string `json:"contract_addr"`
|
||
|
LiquidityToken string `json:"liquidity_token"`
|
||
|
}
|
||
|
var r response
|
||
|
err = p.QueryStore(ctx, q, &r)
|
||
|
if err != nil {
|
||
|
return nil, nil, errors.Wrap(err, "querying contract store")
|
||
|
}
|
||
|
|
||
|
var assets []AssetInfo
|
||
|
for _, info := range r.AssetInfos {
|
||
|
a, err := p.aiFactory.DecodeFromJson(info)
|
||
|
if err != nil {
|
||
|
return nil, nil, errors.Wrap(err, "decoding asset json")
|
||
|
}
|
||
|
assets = append(assets, a)
|
||
|
}
|
||
|
|
||
|
if len(assets) < 2 {
|
||
|
return nil, nil, errors.Errorf("not enough token in pair config: %d", len(assets))
|
||
|
}
|
||
|
t1, err := GetTokenFromAssetInfo(ctx, p.Contract.Querier(), assets[0])
|
||
|
if err != nil {
|
||
|
return nil, nil, errors.Wrap(err, "getting token1 from asset info")
|
||
|
}
|
||
|
|
||
|
t2, err := GetTokenFromAssetInfo(ctx, p.Contract.Querier(), assets[1])
|
||
|
if err != nil {
|
||
|
return nil, nil, errors.Wrap(err, "getting token2 from asset info")
|
||
|
}
|
||
|
|
||
|
lptoken, err := Cw20TokenFromAddress(ctx, p.Contract.Querier(), r.LiquidityToken)
|
||
|
if err != nil {
|
||
|
return nil, nil, errors.Wrapf(err, "getting lp token: %s", r.LiquidityToken)
|
||
|
}
|
||
|
|
||
|
return []Token{t1, t2}, lptoken, nil
|
||
|
}
|
||
|
|
||
|
type PoolInfo map[string]decimal.Decimal
|
||
|
|
||
|
func (p BasePair) PoolInfo(ctx context.Context) (PoolInfo, error) {
|
||
|
type query struct {
|
||
|
Pool struct {
|
||
|
} `json:"pool"`
|
||
|
}
|
||
|
type response struct {
|
||
|
Assets []struct {
|
||
|
Info json.RawMessage `json:"info"`
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
} `json:"assets"`
|
||
|
TotalShare decimal.Decimal `json:"total_share"`
|
||
|
}
|
||
|
var r response
|
||
|
err := p.QueryStore(ctx, query{}, &r)
|
||
|
if err != nil {
|
||
|
return PoolInfo{}, errors.Wrap(err, "querying contract store")
|
||
|
}
|
||
|
res := make(map[string]decimal.Decimal)
|
||
|
for _, a := range r.Assets {
|
||
|
ai, err := p.aiFactory.DecodeFromJson(a.Info)
|
||
|
if err != nil {
|
||
|
return PoolInfo{}, errors.Wrap(err, "decoding asset info")
|
||
|
}
|
||
|
if p.token1.Id() == ai.Id() {
|
||
|
res[ai.Id()] = p.token1.ValueFromTerra(a.Amount)
|
||
|
} else if p.token2.Id() == ai.Id() {
|
||
|
res[ai.Id()] = p.token2.ValueFromTerra(a.Amount)
|
||
|
} else {
|
||
|
return PoolInfo{}, errors.New("asset unknown")
|
||
|
}
|
||
|
}
|
||
|
return res, nil
|
||
|
}
|
||
|
|
||
|
func (p BasePair) NewWithdrawLiquidityMessage(sender cosmos.AccAddress, amount decimal.Decimal) (cosmos.Msg, error) {
|
||
|
type query struct {
|
||
|
WithdrawLiquidity struct {
|
||
|
} `json:"withdraw_liquidity"`
|
||
|
}
|
||
|
var q query
|
||
|
return p.lpToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||
|
}
|
||
|
|
||
|
func (p BasePair) Share(ctx context.Context, lpAmount decimal.Decimal) (token1Amount decimal.Decimal, token2Amount decimal.Decimal, err error) {
|
||
|
type query struct {
|
||
|
Share struct {
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
} `json:"share"`
|
||
|
}
|
||
|
type response []struct {
|
||
|
Info json.RawMessage `json:"info"`
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
}
|
||
|
var q query
|
||
|
q.Share.Amount = p.lpToken.ValueToTerra(lpAmount)
|
||
|
var r response
|
||
|
err = p.QueryStore(ctx, q, &r)
|
||
|
if err != nil {
|
||
|
return decimal.Zero, decimal.Zero, errors.Wrap(err, "querying contract store")
|
||
|
}
|
||
|
if len(r) < 2 {
|
||
|
return decimal.Zero, decimal.Zero, errors.Errorf("not enough token in share response: %d", len(r))
|
||
|
}
|
||
|
|
||
|
for _, a := range r {
|
||
|
ai, err := p.aiFactory.DecodeFromJson(a.Info)
|
||
|
if err != nil {
|
||
|
return decimal.Zero, decimal.Zero, errors.Wrap(err, "decoding asset info")
|
||
|
}
|
||
|
if p.token1.Id() == ai.Id() {
|
||
|
token1Amount = p.token1.ValueFromTerra(a.Amount)
|
||
|
} else if p.token2.Id() == ai.Id() {
|
||
|
token2Amount = p.token2.ValueFromTerra(a.Amount)
|
||
|
} else {
|
||
|
return decimal.Zero, decimal.Zero, errors.New("asset unknown")
|
||
|
}
|
||
|
}
|
||
|
return token1Amount, token2Amount, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func (p BasePair) NewSimpleSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal) (cosmos.Msg, error) {
|
||
|
type query struct {
|
||
|
Swap struct {
|
||
|
OfferAsset struct {
|
||
|
Info AssetInfo `json:"info"`
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
} `json:"offer_asset"`
|
||
|
} `json:"swap"`
|
||
|
}
|
||
|
|
||
|
var q query
|
||
|
q.Swap.OfferAsset.Amount = offerToken.ValueToTerra(amount)
|
||
|
q.Swap.OfferAsset.Info = p.aiFactory.NewFromToken(offerToken)
|
||
|
return offerToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||
|
}
|
||
|
|
||
|
func (p BasePair) NewSwapMessage(sender cosmos.AccAddress, offerToken Token, amount decimal.Decimal, spread string, beliefPrice decimal.Decimal) (cosmos.Msg, error) {
|
||
|
type query struct {
|
||
|
Swap struct {
|
||
|
MaxSpread string `json:"max_spread"`
|
||
|
OfferAsset struct {
|
||
|
Info AssetInfo `json:"info"`
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
} `json:"offer_asset"`
|
||
|
BeliefPrice decimal.Decimal `json:"belief_price"`
|
||
|
} `json:"swap"`
|
||
|
}
|
||
|
|
||
|
var q query
|
||
|
q.Swap.OfferAsset.Info = p.aiFactory.NewFromToken(offerToken)
|
||
|
q.Swap.OfferAsset.Amount = offerToken.ValueToTerra(amount)
|
||
|
q.Swap.MaxSpread = spread
|
||
|
q.Swap.BeliefPrice = beliefPrice
|
||
|
return offerToken.NewMsgSendExecute(sender, p.Contract, amount, q)
|
||
|
}
|
||
|
|
||
|
func ComputeConstantProductSwap(offerPool decimal.Decimal, askPool decimal.Decimal, offerAmount decimal.Decimal, commissionRate decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, decimal.Decimal) {
|
||
|
if offerPool.Equals(decimal.Zero) || askPool.Equals(decimal.Zero) || offerAmount.Equals(decimal.Zero) {
|
||
|
return decimal.Zero, decimal.Zero, decimal.Zero, decimal.Zero
|
||
|
}
|
||
|
cp := offerPool.Mul(askPool)
|
||
|
returnAmount := (askPool.Sub(cp.Div(offerPool.Add(offerAmount)))).Truncate(0)
|
||
|
spread := offerAmount.Mul(askPool).Div(offerPool).Sub(returnAmount).Truncate(0)
|
||
|
commissionAmount := returnAmount.Mul(commissionRate).Truncate(0)
|
||
|
beliefPrice := offerAmount.Div(returnAmount)
|
||
|
returnAmount = returnAmount.Sub(commissionAmount)
|
||
|
return returnAmount, spread, commissionAmount, beliefPrice
|
||
|
}
|
||
|
|
||
|
func (p BasePair) SimulateSwap(ctx context.Context, offer Token, amount decimal.Decimal) (decimal.Decimal, decimal.Decimal, decimal.Decimal, error) {
|
||
|
type query struct {
|
||
|
Simulation struct {
|
||
|
OfferAsset struct {
|
||
|
Info AssetInfo `json:"info"`
|
||
|
Amount decimal.Decimal `json:"amount"`
|
||
|
} `json:"offer_asset"`
|
||
|
} `json:"simulation"`
|
||
|
}
|
||
|
var q query
|
||
|
q.Simulation.OfferAsset.Info = p.aiFactory.NewFromToken(offer)
|
||
|
q.Simulation.OfferAsset.Amount = offer.ValueToTerra(amount)
|
||
|
type response struct {
|
||
|
ReturnAmount decimal.Decimal `json:"return_amount"`
|
||
|
SpreadAmount decimal.Decimal `json:"spread_amount"`
|
||
|
CommissionAmount decimal.Decimal `json:"commission_amount"`
|
||
|
}
|
||
|
var r response
|
||
|
err := p.QueryStore(ctx, q, &r)
|
||
|
if err != nil {
|
||
|
return decimal.Zero, decimal.Zero, decimal.Zero, errors.Wrap(err, "querying contract store")
|
||
|
}
|
||
|
|
||
|
return r.ReturnAmount, r.SpreadAmount, r.CommissionAmount, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
func (p *BasePair) Equals(pair Pair) bool {
|
||
|
return p.contractAddress.String() == pair.ContractAddressString()
|
||
|
}
|
||
|
|
||
|
func (p *BasePair) String() string {
|
||
|
t1symbol := ""
|
||
|
if p.token1 != nil {
|
||
|
t1symbol = p.token1.Symbol()
|
||
|
}
|
||
|
t2symbol := ""
|
||
|
if p.token2 != nil {
|
||
|
t2symbol = p.token2.Symbol()
|
||
|
}
|
||
|
return fmt.Sprintf("%s / %s", t1symbol, t2symbol)
|
||
|
}
|