mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
519 lines
13 KiB
Go
519 lines
13 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
"sync"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// YieldVault represents a yield-generating vault
|
|
type YieldVault struct {
|
|
Address common.Address
|
|
Asset Currency // Underlying asset
|
|
TotalAssets *big.Int // Total assets in vault
|
|
TotalShares *big.Int // Total shares issued
|
|
Strategy VaultStrategy // Active strategy
|
|
PerformanceFee uint32 // Fee on profits (basis points)
|
|
ManagementFee uint32 // Annual management fee (basis points)
|
|
DepositLimit *big.Int // Max deposits
|
|
WithdrawEnabled bool
|
|
DepositEnabled bool
|
|
LastHarvest int64 // Last harvest timestamp
|
|
Strategist common.Address // Strategy manager
|
|
}
|
|
|
|
// VaultStrategy represents a yield strategy
|
|
type VaultStrategy interface {
|
|
// Name returns the strategy name
|
|
Name() string
|
|
|
|
// Deposit deploys assets into the strategy
|
|
Deposit(amount *big.Int) error
|
|
|
|
// Withdraw retrieves assets from the strategy
|
|
Withdraw(amount *big.Int) (*big.Int, error)
|
|
|
|
// Harvest collects and compounds yields
|
|
Harvest() (*big.Int, error)
|
|
|
|
// EstimatedAPY returns expected annual yield
|
|
EstimatedAPY() *big.Int
|
|
|
|
// TotalDeployed returns assets currently deployed
|
|
TotalDeployed() *big.Int
|
|
}
|
|
|
|
// VaultPosition represents a user's position in a vault
|
|
type VaultPosition struct {
|
|
Owner common.Address
|
|
Vault common.Address
|
|
Shares *big.Int
|
|
DepositTime int64
|
|
LastAction int64
|
|
}
|
|
|
|
// VaultManager manages all yield vaults
|
|
type VaultManager struct {
|
|
Vaults map[common.Address]*YieldVault
|
|
Positions map[common.Address]map[common.Address]*VaultPosition // User -> Vault -> Position
|
|
Strategies map[string]VaultStrategy
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewVaultManager creates a new vault manager
|
|
func NewVaultManager() *VaultManager {
|
|
return &VaultManager{
|
|
Vaults: make(map[common.Address]*YieldVault),
|
|
Positions: make(map[common.Address]map[common.Address]*VaultPosition),
|
|
Strategies: make(map[string]VaultStrategy),
|
|
}
|
|
}
|
|
|
|
// CreateVault creates a new yield vault
|
|
func (vm *VaultManager) CreateVault(
|
|
asset Currency,
|
|
strategy VaultStrategy,
|
|
performanceFee uint32,
|
|
managementFee uint32,
|
|
depositLimit *big.Int,
|
|
strategist common.Address,
|
|
) (common.Address, error) {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
|
|
// Validate fees
|
|
if performanceFee > 5000 { // Max 50%
|
|
return common.Address{}, ErrInvalidFee
|
|
}
|
|
if managementFee > 500 { // Max 5%
|
|
return common.Address{}, ErrInvalidFee
|
|
}
|
|
|
|
// Generate vault address (in production, would be CREATE2)
|
|
vaultAddr := generateVaultAddress(asset, strategy.Name())
|
|
|
|
if _, exists := vm.Vaults[vaultAddr]; exists {
|
|
return common.Address{}, ErrVaultExists
|
|
}
|
|
|
|
vault := &YieldVault{
|
|
Address: vaultAddr,
|
|
Asset: asset,
|
|
TotalAssets: big.NewInt(0),
|
|
TotalShares: big.NewInt(0),
|
|
Strategy: strategy,
|
|
PerformanceFee: performanceFee,
|
|
ManagementFee: managementFee,
|
|
DepositLimit: depositLimit,
|
|
WithdrawEnabled: true,
|
|
DepositEnabled: true,
|
|
LastHarvest: 0,
|
|
Strategist: strategist,
|
|
}
|
|
|
|
vm.Vaults[vaultAddr] = vault
|
|
return vaultAddr, nil
|
|
}
|
|
|
|
// Deposit deposits assets into a vault
|
|
func (vm *VaultManager) Deposit(user common.Address, vault common.Address, assets *big.Int) (*big.Int, error) {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
if !v.DepositEnabled {
|
|
return nil, ErrDepositsDisabled
|
|
}
|
|
|
|
// Check deposit limit
|
|
newTotal := new(big.Int).Add(v.TotalAssets, assets)
|
|
if v.DepositLimit.Sign() > 0 && newTotal.Cmp(v.DepositLimit) > 0 {
|
|
return nil, ErrDepositLimitExceeded
|
|
}
|
|
|
|
// Calculate shares to mint
|
|
var shares *big.Int
|
|
if v.TotalShares.Sign() == 0 {
|
|
// First deposit: 1:1 ratio
|
|
shares = new(big.Int).Set(assets)
|
|
} else {
|
|
// shares = assets * totalShares / totalAssets
|
|
shares = new(big.Int).Mul(assets, v.TotalShares)
|
|
shares.Div(shares, v.TotalAssets)
|
|
}
|
|
|
|
if shares.Sign() == 0 {
|
|
return nil, ErrZeroShares
|
|
}
|
|
|
|
// Update vault state
|
|
v.TotalAssets.Add(v.TotalAssets, assets)
|
|
v.TotalShares.Add(v.TotalShares, shares)
|
|
|
|
// Deploy to strategy
|
|
if err := v.Strategy.Deposit(assets); err != nil {
|
|
// Revert state changes
|
|
v.TotalAssets.Sub(v.TotalAssets, assets)
|
|
v.TotalShares.Sub(v.TotalShares, shares)
|
|
return nil, err
|
|
}
|
|
|
|
// Update user position
|
|
vm.updatePosition(user, vault, shares, true)
|
|
|
|
return shares, nil
|
|
}
|
|
|
|
// Withdraw withdraws assets from a vault
|
|
func (vm *VaultManager) Withdraw(user common.Address, vault common.Address, shares *big.Int) (*big.Int, error) {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
if !v.WithdrawEnabled {
|
|
return nil, ErrWithdrawalsDisabled
|
|
}
|
|
|
|
// Check user has enough shares
|
|
position := vm.getPosition(user, vault)
|
|
if position == nil || position.Shares.Cmp(shares) < 0 {
|
|
return nil, ErrInsufficientShares
|
|
}
|
|
|
|
// Calculate assets to return
|
|
// assets = shares * totalAssets / totalShares
|
|
assets := new(big.Int).Mul(shares, v.TotalAssets)
|
|
assets.Div(assets, v.TotalShares)
|
|
|
|
// Withdraw from strategy
|
|
actualAssets, err := v.Strategy.Withdraw(assets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Update vault state
|
|
v.TotalAssets.Sub(v.TotalAssets, actualAssets)
|
|
v.TotalShares.Sub(v.TotalShares, shares)
|
|
|
|
// Update user position
|
|
vm.updatePosition(user, vault, shares, false)
|
|
|
|
return actualAssets, nil
|
|
}
|
|
|
|
// Harvest harvests yields and compounds
|
|
func (vm *VaultManager) Harvest(vault common.Address) (*big.Int, error) {
|
|
vm.mu.Lock()
|
|
defer vm.mu.Unlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
// Harvest from strategy
|
|
profit, err := v.Strategy.Harvest()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if profit.Sign() <= 0 {
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
// Calculate fees
|
|
performanceFee := new(big.Int).Mul(profit, big.NewInt(int64(v.PerformanceFee)))
|
|
performanceFee.Div(performanceFee, big.NewInt(10000))
|
|
|
|
netProfit := new(big.Int).Sub(profit, performanceFee)
|
|
|
|
// Add net profit to total assets (compounds for all shareholders)
|
|
v.TotalAssets.Add(v.TotalAssets, netProfit)
|
|
|
|
return netProfit, nil
|
|
}
|
|
|
|
// GetSharePrice returns current price per share
|
|
func (vm *VaultManager) GetSharePrice(vault common.Address) (*big.Int, error) {
|
|
vm.mu.RLock()
|
|
defer vm.mu.RUnlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
if v.TotalShares.Sign() == 0 {
|
|
return big.NewInt(1e18), nil // 1:1 if no shares
|
|
}
|
|
|
|
// price = totalAssets * 1e18 / totalShares
|
|
price := new(big.Int).Mul(v.TotalAssets, big.NewInt(1e18))
|
|
price.Div(price, v.TotalShares)
|
|
|
|
return price, nil
|
|
}
|
|
|
|
// GetUserAssets returns user's share of assets
|
|
func (vm *VaultManager) GetUserAssets(user common.Address, vault common.Address) (*big.Int, error) {
|
|
vm.mu.RLock()
|
|
defer vm.mu.RUnlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
position := vm.getPosition(user, vault)
|
|
if position == nil {
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
if v.TotalShares.Sign() == 0 {
|
|
return big.NewInt(0), nil
|
|
}
|
|
|
|
// assets = shares * totalAssets / totalShares
|
|
assets := new(big.Int).Mul(position.Shares, v.TotalAssets)
|
|
assets.Div(assets, v.TotalShares)
|
|
|
|
return assets, nil
|
|
}
|
|
|
|
// GetVaultAPY returns estimated APY for a vault
|
|
func (vm *VaultManager) GetVaultAPY(vault common.Address) (*big.Int, error) {
|
|
vm.mu.RLock()
|
|
defer vm.mu.RUnlock()
|
|
|
|
v := vm.Vaults[vault]
|
|
if v == nil {
|
|
return nil, ErrVaultNotFound
|
|
}
|
|
|
|
return v.Strategy.EstimatedAPY(), nil
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func (vm *VaultManager) updatePosition(user common.Address, vault common.Address, shares *big.Int, isDeposit bool) {
|
|
if vm.Positions[user] == nil {
|
|
vm.Positions[user] = make(map[common.Address]*VaultPosition)
|
|
}
|
|
|
|
position := vm.Positions[user][vault]
|
|
if position == nil {
|
|
position = &VaultPosition{
|
|
Owner: user,
|
|
Vault: vault,
|
|
Shares: big.NewInt(0),
|
|
DepositTime: 0,
|
|
}
|
|
vm.Positions[user][vault] = position
|
|
}
|
|
|
|
if isDeposit {
|
|
position.Shares.Add(position.Shares, shares)
|
|
if position.DepositTime == 0 {
|
|
position.DepositTime = currentTimestamp()
|
|
}
|
|
} else {
|
|
position.Shares.Sub(position.Shares, shares)
|
|
if position.Shares.Sign() == 0 {
|
|
delete(vm.Positions[user], vault)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (vm *VaultManager) getPosition(user common.Address, vault common.Address) *VaultPosition {
|
|
userPositions := vm.Positions[user]
|
|
if userPositions == nil {
|
|
return nil
|
|
}
|
|
return userPositions[vault]
|
|
}
|
|
|
|
func generateVaultAddress(asset Currency, strategyName string) common.Address {
|
|
// Simplified - in production use CREATE2
|
|
var addr common.Address
|
|
copy(addr[:], asset.Address[:10])
|
|
copy(addr[10:], []byte(strategyName)[:10])
|
|
return addr
|
|
}
|
|
|
|
func currentTimestamp() int64 {
|
|
return 0 // In production, get from block timestamp
|
|
}
|
|
|
|
// =========================================================================
|
|
// Pre-built Strategies
|
|
// =========================================================================
|
|
|
|
// LPYieldStrategy farms LP tokens for yield
|
|
type LPYieldStrategy struct {
|
|
PoolManager *PoolManager
|
|
PoolID [32]byte
|
|
Deployed *big.Int
|
|
APY *big.Int // Estimated APY in basis points
|
|
}
|
|
|
|
func NewLPYieldStrategy(pm *PoolManager, poolID [32]byte) *LPYieldStrategy {
|
|
return &LPYieldStrategy{
|
|
PoolManager: pm,
|
|
PoolID: poolID,
|
|
Deployed: big.NewInt(0),
|
|
APY: big.NewInt(1000), // 10% default
|
|
}
|
|
}
|
|
|
|
func (s *LPYieldStrategy) Name() string { return "LP_YIELD" }
|
|
|
|
func (s *LPYieldStrategy) Deposit(amount *big.Int) error {
|
|
// Add liquidity to pool
|
|
s.Deployed.Add(s.Deployed, amount)
|
|
return nil
|
|
}
|
|
|
|
func (s *LPYieldStrategy) Withdraw(amount *big.Int) (*big.Int, error) {
|
|
if s.Deployed.Cmp(amount) < 0 {
|
|
return nil, ErrInsufficientLiquidity
|
|
}
|
|
s.Deployed.Sub(s.Deployed, amount)
|
|
return amount, nil
|
|
}
|
|
|
|
func (s *LPYieldStrategy) Harvest() (*big.Int, error) {
|
|
// Collect trading fees from pool
|
|
// Simplified - in production would query actual fees
|
|
profit := new(big.Int).Div(s.Deployed, big.NewInt(100)) // 1% of deployed
|
|
return profit, nil
|
|
}
|
|
|
|
func (s *LPYieldStrategy) EstimatedAPY() *big.Int {
|
|
return new(big.Int).Set(s.APY)
|
|
}
|
|
|
|
func (s *LPYieldStrategy) TotalDeployed() *big.Int {
|
|
return new(big.Int).Set(s.Deployed)
|
|
}
|
|
|
|
// LendingYieldStrategy supplies assets to lending pool
|
|
type LendingYieldStrategy struct {
|
|
LendingPool *LendingPool
|
|
Asset common.Address
|
|
Deployed *big.Int
|
|
}
|
|
|
|
func NewLendingYieldStrategy(lp *LendingPool, asset common.Address) *LendingYieldStrategy {
|
|
return &LendingYieldStrategy{
|
|
LendingPool: lp,
|
|
Asset: asset,
|
|
Deployed: big.NewInt(0),
|
|
}
|
|
}
|
|
|
|
func (s *LendingYieldStrategy) Name() string { return "LENDING_YIELD" }
|
|
|
|
func (s *LendingYieldStrategy) Deposit(amount *big.Int) error {
|
|
s.Deployed.Add(s.Deployed, amount)
|
|
return nil
|
|
}
|
|
|
|
func (s *LendingYieldStrategy) Withdraw(amount *big.Int) (*big.Int, error) {
|
|
if s.Deployed.Cmp(amount) < 0 {
|
|
return nil, ErrInsufficientLiquidity
|
|
}
|
|
s.Deployed.Sub(s.Deployed, amount)
|
|
return amount, nil
|
|
}
|
|
|
|
func (s *LendingYieldStrategy) Harvest() (*big.Int, error) {
|
|
// Collect interest from lending
|
|
// In production, query actual accrued interest
|
|
profit := new(big.Int).Div(s.Deployed, big.NewInt(200)) // 0.5% of deployed
|
|
return profit, nil
|
|
}
|
|
|
|
func (s *LendingYieldStrategy) EstimatedAPY() *big.Int {
|
|
// Return supply APY from lending pool
|
|
// In production, calculate from actual rates
|
|
return big.NewInt(500) // 5%
|
|
}
|
|
|
|
func (s *LendingYieldStrategy) TotalDeployed() *big.Int {
|
|
return new(big.Int).Set(s.Deployed)
|
|
}
|
|
|
|
// DeltaNeutralStrategy maintains delta-neutral position with funding income
|
|
type DeltaNeutralStrategy struct {
|
|
PerpEngine *PerpetualEngine
|
|
SpotDEX *PoolManager
|
|
MarketID [32]byte
|
|
Deployed *big.Int
|
|
}
|
|
|
|
func NewDeltaNeutralStrategy(pe *PerpetualEngine, spot *PoolManager, marketID [32]byte) *DeltaNeutralStrategy {
|
|
return &DeltaNeutralStrategy{
|
|
PerpEngine: pe,
|
|
SpotDEX: spot,
|
|
MarketID: marketID,
|
|
Deployed: big.NewInt(0),
|
|
}
|
|
}
|
|
|
|
func (s *DeltaNeutralStrategy) Name() string { return "DELTA_NEUTRAL" }
|
|
|
|
func (s *DeltaNeutralStrategy) Deposit(amount *big.Int) error {
|
|
// Buy spot + short perp to create delta-neutral position
|
|
s.Deployed.Add(s.Deployed, amount)
|
|
return nil
|
|
}
|
|
|
|
func (s *DeltaNeutralStrategy) Withdraw(amount *big.Int) (*big.Int, error) {
|
|
if s.Deployed.Cmp(amount) < 0 {
|
|
return nil, ErrInsufficientLiquidity
|
|
}
|
|
// Unwind delta-neutral position
|
|
s.Deployed.Sub(s.Deployed, amount)
|
|
return amount, nil
|
|
}
|
|
|
|
func (s *DeltaNeutralStrategy) Harvest() (*big.Int, error) {
|
|
// Collect funding payments (shorts receive when funding > 0)
|
|
// In production, query actual funding received
|
|
profit := new(big.Int).Div(s.Deployed, big.NewInt(50)) // 2% of deployed
|
|
return profit, nil
|
|
}
|
|
|
|
func (s *DeltaNeutralStrategy) EstimatedAPY() *big.Int {
|
|
// Funding rate based APY
|
|
return big.NewInt(2000) // 20%
|
|
}
|
|
|
|
func (s *DeltaNeutralStrategy) TotalDeployed() *big.Int {
|
|
return new(big.Int).Set(s.Deployed)
|
|
}
|
|
|
|
// Additional errors for vaults (ErrInvalidFee in types.go)
|
|
var (
|
|
ErrVaultExists = errors.New("vault already exists")
|
|
ErrVaultNotFound = errors.New("vault not found")
|
|
ErrDepositsDisabled = errors.New("deposits are disabled")
|
|
ErrWithdrawalsDisabled = errors.New("withdrawals are disabled")
|
|
ErrDepositLimitExceeded = errors.New("deposit limit exceeded")
|
|
ErrZeroShares = errors.New("would mint zero shares")
|
|
ErrInsufficientShares = errors.New("insufficient shares")
|
|
)
|