package aave import ( "context" "math/big" "time" "git.lehouerou.net/laurent/evm" "git.lehouerou.net/laurent/evm/aave/contracts" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/shopspring/decimal" ) const ( SECONDSPERYEAR = 31536000 ) type LendingPool struct { client evm.Client contract *contracts.LendingPool } func NewLendingPool(client evm.Client, address common.Address) (*LendingPool, error) { contract, err := contracts.NewLendingPool(address, client) if err != nil { return nil, errors.Wrap(err, "init contract") } return &LendingPool{ client: client, contract: contract, }, nil } type UserAccountData struct { TotalCollateralETH decimal.Decimal TotalDebtETH decimal.Decimal AvailableBorrowsETH decimal.Decimal CurrentLiquidationThreshold decimal.Decimal CurrentLtv decimal.Decimal MaxLtv decimal.Decimal HealthFactor decimal.Decimal } func (uad UserAccountData) BorrowAmountToLtv(ltv decimal.Decimal) decimal.Decimal { return uad.TotalCollateralETH.Mul(ltv).Div(decimal.NewFromInt(100)).Sub(uad.TotalDebtETH) } func (lp *LendingPool) UserAccountDataForAddress(address common.Address) (UserAccountData, error) { uad, err := lp.contract.GetUserAccountData(&bind.CallOpts{}, address) if err != nil { return UserAccountData{}, errors.Wrap(err, "calling contract") } res := UserAccountData{ TotalCollateralETH: decimal.NewFromBigInt(uad.TotalCollateralETH, -8), TotalDebtETH: decimal.NewFromBigInt(uad.TotalDebtETH, -8), AvailableBorrowsETH: decimal.NewFromBigInt(uad.AvailableBorrowsETH, -8), CurrentLiquidationThreshold: decimal.NewFromBigInt(uad.CurrentLiquidationThreshold, -2), MaxLtv: decimal.NewFromBigInt(uad.Ltv, -2), HealthFactor: decimal.NewFromBigInt(uad.HealthFactor, -18), } res.CurrentLtv = res.TotalDebtETH.Div(res.TotalCollateralETH).Mul(decimal.NewFromInt(100)) return res, nil } type ReserveData struct { LiquidityIndex decimal.Decimal VariableBorrowIndex decimal.Decimal CurrentLiquidityRate decimal.Decimal CurrentVariableBorrowRate decimal.Decimal CurrentStableBorrowRate decimal.Decimal LastUpdateTimestamp time.Time ATokenAddress common.Address StableDebtTokenAddress common.Address VariableDebtTokenAddress common.Address InterestRateStrategyAddress common.Address Id uint8 } func (lp *LendingPool) ReserveData(asset evm.Token) (ReserveData, error) { rd, err := lp.contract.GetReserveData(&bind.CallOpts{}, asset.Address()) if err != nil { return ReserveData{}, errors.Wrap(err, "calling contract") } res := ReserveData{ LiquidityIndex: decimal.NewFromBigInt(rd.LiquidityIndex, -27), VariableBorrowIndex: decimal.NewFromBigInt(rd.VariableBorrowIndex, -27), CurrentLiquidityRate: decimal.NewFromBigInt(rd.CurrentLiquidityRate, -27), CurrentVariableBorrowRate: decimal.NewFromBigInt(rd.CurrentVariableBorrowRate, -27), CurrentStableBorrowRate: decimal.NewFromBigInt(rd.CurrentStableBorrowRate, -27), LastUpdateTimestamp: time.Unix(rd.LastUpdateTimestamp.Int64(), 0), ATokenAddress: rd.ATokenAddress, StableDebtTokenAddress: rd.StableDebtTokenAddress, VariableDebtTokenAddress: rd.VariableDebtTokenAddress, InterestRateStrategyAddress: rd.InterestRateStrategyAddress, Id: rd.Id, } return res, nil } func (lp *LendingPool) Borrow(ctx context.Context, asset evm.Token, amount decimal.Decimal, opts ...evm.ExecutionOption) (evm.Transaction, error) { return lp.client.Execute(ctx, func(ctx context.Context, opts *evm.TransactOpts) (*types.Transaction, error) { return lp.contract.Borrow(opts.TransactOpts, asset.Address(), asset.ValueToBigInt(amount), big.NewInt(2), 0, lp.client.PublicAddress()) }, opts...) } func (lp *LendingPool) Repay(ctx context.Context, asset evm.Token, amount decimal.Decimal, opts ...evm.ExecutionOption) (evm.Transaction, error) { balance, err := asset.Balance() if err != nil { return nil, errors.Wrap(err, "getting token balance") } if balance.LessThan(amount) { return nil, errors.Wrapf(err, "balance of %s insufficient. need %s have %s", asset.Symbol(), amount, balance) } return lp.client.Execute(ctx, func(ctx context.Context, opts *evm.TransactOpts) (*types.Transaction, error) { return lp.contract.Repay(opts.TransactOpts, asset.Address(), asset.ValueToBigInt(amount), big.NewInt(2), lp.client.PublicAddress()) }, opts...) }