evm/curve/Pool.go
2021-11-15 14:09:43 +04:00

158 lines
4.8 KiB
Go

package curve
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/core/types"
"git.lehouerou.net/laurent/evm"
"git.lehouerou.net/laurent/evm/curve/contracts"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
"github.com/shopspring/decimal"
)
type Pool struct {
client evm.Client
contract *contracts.Pool
lptoken evm.Token
tokens []evm.Token
}
func NewPool(client evm.Client, address common.Address) (*Pool, error) {
contract, err := contracts.NewPool(address, client)
if err != nil {
return nil, errors.Wrap(err, "init contract")
}
lptokenaddress, err := contract.LpToken(&bind.CallOpts{})
if err != nil {
return nil, errors.Wrap(err, "getting lp token address")
}
lptoken, err := client.TokenService().TokenByAddress(lptokenaddress)
if err != nil {
return nil, errors.Wrap(err, "getting lp token")
}
p := &Pool{
client: client,
contract: contract,
lptoken: lptoken,
}
tokens, err := p.getTokens()
if err != nil {
return nil, errors.Wrap(err, "getting underlying tokens")
}
p.tokens = tokens
return p, nil
}
func (p *Pool) getTokens() ([]evm.Token, error) {
var res []evm.Token
var index int64
for {
t, err := p.contract.UnderlyingCoins(&bind.CallOpts{}, big.NewInt(index))
if err != nil {
break
}
token, err := p.client.TokenService().TokenByAddress(t)
if err != nil {
return nil, errors.Wrapf(err, "getting token %s", t)
}
res = append(res, token)
index++
}
return res, nil
}
func (p *Pool) VirtualPrice() (decimal.Decimal, error) {
price, err := p.contract.GetVirtualPrice(&bind.CallOpts{})
if err != nil {
return decimal.Zero, errors.Wrap(err, "calling contract")
}
return decimal.NewFromBigInt(price, -18), nil
}
func (p *Pool) CalculateTokenAmountForOneUnderlying(token evm.Token, amount decimal.Decimal, isdeposit bool) (decimal.Decimal, error) {
tokenindex, err := p.getTokenIndex(token)
if err != nil {
return decimal.Zero, errors.Wrap(err, "getting token index")
}
var amounts [3]*big.Int
for i, _ := range p.tokens {
if i == tokenindex {
amounts[i] = token.ValueToBigInt(amount)
} else {
amounts[i] = big.NewInt(0)
}
}
raw, err := p.contract.CalcTokenAmount(&bind.CallOpts{}, amounts, isdeposit)
if err != nil {
return decimal.Zero, errors.Wrap(err, "calling contract")
}
return p.lptoken.ValueFromBigInt(raw), nil
}
func (p *Pool) CalculateWithdrawOneCoin(token evm.Token, amount decimal.Decimal) (decimal.Decimal, error) {
tokenindex, err := p.getTokenIndex(token)
if err != nil {
return decimal.Zero, errors.Wrap(err, "getting token index")
}
raw, err := p.contract.CalcWithdrawOneCoin(&bind.CallOpts{}, p.lptoken.ValueToBigInt(amount), big.NewInt(int64(tokenindex)))
if err != nil {
return decimal.Zero, errors.Wrap(err, "calling contract")
}
return token.ValueFromBigInt(raw), nil
}
func (p *Pool) getTokenIndex(token evm.Token) (int, error) {
tokenindex := -1
for i, t := range p.tokens {
if t.Address() == token.Address() {
tokenindex = i
break
}
}
if tokenindex == -1 {
return -1, errors.Errorf("token %s not found in pool", token.Symbol())
}
return tokenindex, nil
}
func (p *Pool) AddLiquidity(ctx context.Context, token evm.Token, amount decimal.Decimal, opts ...evm.ExecutionOption) (evm.Transaction, error) {
tokenindex, err := p.getTokenIndex(token)
if err != nil {
return nil, errors.Wrap(err, "getting token index")
}
amounts := [3]*big.Int{big.NewInt(0), big.NewInt(0), big.NewInt(0)}
amounts[tokenindex] = token.ValueToBigInt(amount)
minMintAmount, err := p.CalculateTokenAmountForOneUnderlying(token, amount, true)
if err != nil {
return nil, errors.Wrap(err, "getting mint amount")
}
minMintAmount = minMintAmount.Sub(minMintAmount.Mul(decimal.NewFromFloat(0.01))).Round(int32(p.lptoken.Decimals()))
return p.client.Execute(ctx, func(ctx context.Context, opts *evm.TransactOpts) (*types.Transaction, error) {
return p.contract.AddLiquidity0(opts.TransactOpts, amounts, p.lptoken.ValueToBigInt(minMintAmount), true)
}, opts...)
}
func (p *Pool) RemoveLiquidity(ctx context.Context, token evm.Token, amount decimal.Decimal, opts ...evm.ExecutionOption) (evm.Transaction, error) {
tokenindex, err := p.getTokenIndex(token)
if err != nil {
return nil, errors.Wrap(err, "getting token index")
}
minamount, err := p.CalculateWithdrawOneCoin(token, amount)
if err != nil {
return nil, errors.Wrap(err, "calculate withdraw one coin")
}
minamount = minamount.Sub(minamount.Mul(decimal.NewFromFloat(0.002))).Round(int32(token.Decimals()))
return p.client.Execute(ctx, func(ctx context.Context, opts *evm.TransactOpts) (*types.Transaction, error) {
return p.contract.RemoveLiquidityOneCoin0(opts.TransactOpts, p.lptoken.ValueToBigInt(amount), big.NewInt(int64(tokenindex)), token.ValueToBigInt(minamount), true)
}, opts...)
}