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...) }