158 lines
4.8 KiB
Go
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...)
|
|
}
|