mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
650 lines
22 KiB
Go
650 lines
22 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package dex
|
|
|
|
import (
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/holiman/uint256"
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// Test helpers
|
|
var (
|
|
testYieldToken = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
testLiquidToken = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
testUser1 = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
|
testUser2 = common.HexToAddress("0x4444444444444444444444444444444444444444")
|
|
testUnderlying = Currency{Address: common.HexToAddress("0x5555555555555555555555555555555555555555")}
|
|
)
|
|
|
|
// Helper to create large big.Int values
|
|
func bigInt(s string) *big.Int {
|
|
n, _ := new(big.Int).SetString(s, 10)
|
|
return n
|
|
}
|
|
|
|
// Helper to set balance
|
|
func setBalance(stateDB *MockStateDB, addr common.Address, amount *big.Int) {
|
|
u256, _ := uint256.FromBig(amount)
|
|
stateDB.balances[addr] = u256
|
|
}
|
|
|
|
// =========================================================================
|
|
// Liquid Tests
|
|
// =========================================================================
|
|
|
|
func TestLiquid_NewLiquid(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
|
|
if alchemist == nil {
|
|
t.Fatal("NewLiquid returned nil")
|
|
}
|
|
if alchemist.yieldTokens == nil {
|
|
t.Fatal("yieldTokens map not initialized")
|
|
}
|
|
if alchemist.liquidTokens == nil {
|
|
t.Fatal("liquidTokens map not initialized")
|
|
}
|
|
if alchemist.accounts == nil {
|
|
t.Fatal("accounts map not initialized")
|
|
}
|
|
}
|
|
|
|
func TestLiquid_AddYieldToken(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Add yield token
|
|
yieldPerBlock := bigInt("1000000000000000") // 0.001 per block
|
|
err := alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
if err != nil {
|
|
t.Fatalf("AddYieldToken failed: %v", err)
|
|
}
|
|
|
|
// Verify it was added
|
|
yt := alchemist.yieldTokens[testYieldToken]
|
|
if yt == nil {
|
|
t.Fatal("yield token not found in map")
|
|
}
|
|
if !yt.IsActive {
|
|
t.Fatal("yield token should be active")
|
|
}
|
|
if yt.YieldPerBlock.Cmp(yieldPerBlock) != 0 {
|
|
t.Fatal("yield per block mismatch")
|
|
}
|
|
|
|
// Try adding same token again - should fail
|
|
err = alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
if err == nil {
|
|
t.Fatal("adding duplicate yield token should fail")
|
|
}
|
|
}
|
|
|
|
func TestLiquid_AddLiquidToken(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Add liquid token
|
|
debtCeiling := bigInt("1000000000000000000000000") // 1M tokens
|
|
err := alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
if err != nil {
|
|
t.Fatalf("AddLiquidToken failed: %v", err)
|
|
}
|
|
|
|
// Verify it was added
|
|
st := alchemist.liquidTokens[testLiquidToken]
|
|
if st == nil {
|
|
t.Fatal("liquid token not found in map")
|
|
}
|
|
if st.DebtCeiling.Cmp(debtCeiling) != 0 {
|
|
t.Fatal("debt ceiling mismatch")
|
|
}
|
|
}
|
|
|
|
func TestLiquid_Deposit(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
|
|
// Give user some balance
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000")) // 1000 tokens
|
|
|
|
// Deposit
|
|
depositAmount := bigInt("100000000000000000000") // 100 tokens
|
|
err := alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
if err != nil {
|
|
t.Fatalf("Deposit failed: %v", err)
|
|
}
|
|
|
|
// Check account
|
|
account := alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
if account == nil {
|
|
t.Fatal("account not created")
|
|
}
|
|
if account.Collateral.Cmp(depositAmount) != 0 {
|
|
t.Fatalf("collateral mismatch: got %s, want %s", account.Collateral, depositAmount)
|
|
}
|
|
if account.Debt.Sign() != 0 {
|
|
t.Fatal("debt should be zero")
|
|
}
|
|
|
|
// Check yield token total deposited
|
|
yt := alchemist.yieldTokens[testYieldToken]
|
|
if yt.TotalDeposited.Cmp(depositAmount) != 0 {
|
|
t.Fatal("total deposited mismatch")
|
|
}
|
|
}
|
|
|
|
func TestLiquid_Mint_MaxLTV(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Deposit collateral
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000") // 100 tokens
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
// Calculate max mintable (90% of collateral)
|
|
maxMintable := new(big.Int).Mul(depositAmount, big.NewInt(MaxLTV))
|
|
maxMintable.Div(maxMintable, big.NewInt(LTVPrecision))
|
|
|
|
// Mint at max LTV
|
|
err := alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, maxMintable)
|
|
if err != nil {
|
|
t.Fatalf("Mint at max LTV failed: %v", err)
|
|
}
|
|
|
|
// Check account
|
|
account := alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
if account.Debt.Cmp(maxMintable) != 0 {
|
|
t.Fatalf("debt mismatch: got %s, want %s", account.Debt, maxMintable)
|
|
}
|
|
|
|
// Verify LTV is 90%
|
|
ltv := alchemist.GetLTV(stateDB, testUser1, testYieldToken)
|
|
if ltv.Int64() != MaxLTV {
|
|
t.Fatalf("LTV mismatch: got %d, want %d", ltv.Int64(), MaxLTV)
|
|
}
|
|
|
|
// Try to mint more - should fail
|
|
err = alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, big.NewInt(1))
|
|
if err != ErrMaxLTVExceeded {
|
|
t.Fatalf("expected ErrMaxLTVExceeded, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLiquid_Burn(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Deposit and mint
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000")
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
mintAmount := bigInt("50000000000000000000") // 50 tokens (50% LTV)
|
|
alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, mintAmount)
|
|
|
|
// Get initial debt
|
|
account := alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
initialDebt := new(big.Int).Set(account.Debt)
|
|
|
|
// Burn half
|
|
burnAmount := bigInt("20000000000000000000") // 20 tokens
|
|
err := alchemist.Burn(stateDB, testUser1, testYieldToken, testLiquidToken, burnAmount)
|
|
if err != nil {
|
|
t.Fatalf("Burn failed: %v", err)
|
|
}
|
|
|
|
// Check debt reduced (accounting for burn fee)
|
|
account = alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
// BurnFee is 10, divisor is 1,000,000 (6 decimal precision)
|
|
fee := new(big.Int).Mul(burnAmount, big.NewInt(10))
|
|
fee.Div(fee, big.NewInt(1_000_000))
|
|
debtReduction := new(big.Int).Sub(burnAmount, fee)
|
|
expectedDebt := new(big.Int).Sub(initialDebt, debtReduction)
|
|
|
|
if account.Debt.Cmp(expectedDebt) != 0 {
|
|
t.Fatalf("debt not reduced properly: expected %v, got %v", expectedDebt, account.Debt)
|
|
}
|
|
}
|
|
|
|
func TestLiquid_Withdraw_WithDebt(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Deposit and mint at 50% LTV
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000") // 100 tokens
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
mintAmount := bigInt("50000000000000000000") // 50 tokens (50% LTV)
|
|
alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, mintAmount)
|
|
|
|
// Try to withdraw too much (would breach 90% LTV)
|
|
// With 50 debt and 90% max LTV, need at least 55.56 collateral
|
|
// So can withdraw at most ~44.44 tokens
|
|
withdrawAmount := bigInt("50000000000000000000") // 50 tokens - should fail
|
|
err := alchemist.Withdraw(stateDB, testUser1, testYieldToken, withdrawAmount)
|
|
if err != ErrMaxLTVExceeded {
|
|
t.Fatalf("expected ErrMaxLTVExceeded, got %v", err)
|
|
}
|
|
|
|
// Withdraw smaller amount (should succeed)
|
|
withdrawAmount = bigInt("40000000000000000000") // 40 tokens
|
|
err = alchemist.Withdraw(stateDB, testUser1, testYieldToken, withdrawAmount)
|
|
if err != nil {
|
|
t.Fatalf("small withdraw failed: %v", err)
|
|
}
|
|
|
|
// Verify collateral reduced
|
|
account := alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
expectedCollateral := new(big.Int).Sub(depositAmount, withdrawAmount)
|
|
if account.Collateral.Cmp(expectedCollateral) != 0 {
|
|
t.Fatalf("collateral mismatch: got %s, want %s", account.Collateral, expectedCollateral)
|
|
}
|
|
}
|
|
|
|
func TestLiquid_GetMaxMintable(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Deposit
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000") // 100 tokens
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
// Check max mintable = 90% of collateral
|
|
maxMintable := alchemist.GetMaxMintable(stateDB, testUser1, testYieldToken)
|
|
expected := new(big.Int).Mul(depositAmount, big.NewInt(MaxLTV))
|
|
expected.Div(expected, big.NewInt(LTVPrecision))
|
|
|
|
if maxMintable.Cmp(expected) != 0 {
|
|
t.Fatalf("max mintable mismatch: got %s, want %s", maxMintable, expected)
|
|
}
|
|
|
|
// Mint some
|
|
mintAmount := bigInt("50000000000000000000")
|
|
alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, mintAmount)
|
|
|
|
// Check max mintable reduced
|
|
maxMintable = alchemist.GetMaxMintable(stateDB, testUser1, testYieldToken)
|
|
expectedRemaining := new(big.Int).Sub(expected, mintAmount)
|
|
if maxMintable.Cmp(expectedRemaining) != 0 {
|
|
t.Fatalf("remaining mintable mismatch: got %s, want %s", maxMintable, expectedRemaining)
|
|
}
|
|
}
|
|
|
|
func TestLiquid_GetTimeToRepayment(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup with known yield rate
|
|
yieldPerBlock := bigInt("1000000000000000") // 0.001 per block per unit
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Deposit 100 tokens
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000") // 100 tokens
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
// Mint 90 tokens (90% LTV)
|
|
mintAmount := bigInt("90000000000000000000")
|
|
alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, mintAmount)
|
|
|
|
// Calculate expected time to repayment
|
|
// Yield per block = collateral * yieldPerBlock / 1e18 = 100e18 * 1e15 / 1e18 = 1e17
|
|
// Time = debt / yieldPerBlock = 90e18 / 1e17 = 900 blocks
|
|
timeToRepay := alchemist.GetTimeToRepayment(stateDB, testUser1, testYieldToken)
|
|
|
|
// Allow some tolerance for fees
|
|
if timeToRepay < 800 || timeToRepay > 1000 {
|
|
t.Fatalf("time to repayment unexpected: got %d blocks", timeToRepay)
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Transmuter Tests
|
|
// =========================================================================
|
|
|
|
func TestTransmuter_NewTransmuter(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
|
|
if transmuter == nil {
|
|
t.Fatal("NewTransmuter returned nil")
|
|
}
|
|
if transmuter.states == nil {
|
|
t.Fatal("states map not initialized")
|
|
}
|
|
if transmuter.stakes == nil {
|
|
t.Fatal("stakes map not initialized")
|
|
}
|
|
}
|
|
|
|
func TestTransmuter_InitializeTransmuter(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
err := transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
if err != nil {
|
|
t.Fatalf("InitializeTransmuter failed: %v", err)
|
|
}
|
|
|
|
state := transmuter.GetLiquidFXState(testLiquidToken)
|
|
if state == nil {
|
|
t.Fatal("transmuter state not found")
|
|
}
|
|
if state.LiquidToken != testLiquidToken {
|
|
t.Fatal("liquid token mismatch")
|
|
}
|
|
if state.ExchangeBuffer.Sign() != 0 {
|
|
t.Fatal("exchange buffer should start at 0")
|
|
}
|
|
}
|
|
|
|
func TestTransmuter_Stake(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
|
|
// Stake
|
|
stakeAmount := bigInt("100000000000000000000")
|
|
err := transmuter.Stake(stateDB, testUser1, testLiquidToken, stakeAmount)
|
|
if err != nil {
|
|
t.Fatalf("Stake failed: %v", err)
|
|
}
|
|
|
|
// Check stake
|
|
stake := transmuter.GetStake(stateDB, testUser1, testLiquidToken)
|
|
if stake == nil {
|
|
t.Fatal("stake not found")
|
|
}
|
|
if stake.StakedAmount.Cmp(stakeAmount) != 0 {
|
|
t.Fatalf("staked amount mismatch: got %s, want %s", stake.StakedAmount, stakeAmount)
|
|
}
|
|
|
|
// Check total staked
|
|
state := transmuter.GetLiquidFXState(testLiquidToken)
|
|
if state.TotalStaked.Cmp(stakeAmount) != 0 {
|
|
t.Fatal("total staked mismatch")
|
|
}
|
|
}
|
|
|
|
func TestTransmuter_Deposit_And_Claim(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
setBalance(stateDB, transmuterAddr, bigInt("1000000000000000000000")) // Give transmuter some underlying
|
|
|
|
// User stakes liquid
|
|
stakeAmount := bigInt("100000000000000000000")
|
|
transmuter.Stake(stateDB, testUser1, testLiquidToken, stakeAmount)
|
|
|
|
// Deposit underlying into transmuter (simulating yield flow)
|
|
depositAmount := bigInt("50000000000000000000") // 50% of staked
|
|
transmuter.Deposit(stateDB, testLiquidToken, depositAmount)
|
|
|
|
// Check claimable
|
|
claimable := transmuter.GetClaimable(stateDB, testUser1, testLiquidToken)
|
|
if claimable.Cmp(depositAmount) != 0 {
|
|
t.Fatalf("claimable mismatch: got %s, want %s", claimable, depositAmount)
|
|
}
|
|
|
|
// Claim
|
|
claimed, err := transmuter.Claim(stateDB, testUser1, testLiquidToken)
|
|
if err != nil {
|
|
t.Fatalf("Claim failed: %v", err)
|
|
}
|
|
if claimed.Cmp(depositAmount) != 0 {
|
|
t.Fatalf("claimed amount mismatch: got %s, want %s", claimed, depositAmount)
|
|
}
|
|
|
|
// Check stake reduced (liquidTokens converted)
|
|
stake := transmuter.GetStake(stateDB, testUser1, testLiquidToken)
|
|
expectedRemaining := new(big.Int).Sub(stakeAmount, depositAmount)
|
|
if stake.StakedAmount.Cmp(expectedRemaining) != 0 {
|
|
t.Fatalf("remaining stake mismatch: got %s, want %s", stake.StakedAmount, expectedRemaining)
|
|
}
|
|
}
|
|
|
|
func TestTransmuter_Unstake(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
|
|
// Stake
|
|
stakeAmount := bigInt("100000000000000000000")
|
|
transmuter.Stake(stateDB, testUser1, testLiquidToken, stakeAmount)
|
|
|
|
// Unstake half
|
|
unstakeAmount := bigInt("50000000000000000000")
|
|
err := transmuter.Unstake(stateDB, testUser1, testLiquidToken, unstakeAmount)
|
|
if err != nil {
|
|
t.Fatalf("Unstake failed: %v", err)
|
|
}
|
|
|
|
// Check remaining stake
|
|
stake := transmuter.GetStake(stateDB, testUser1, testLiquidToken)
|
|
expectedRemaining := new(big.Int).Sub(stakeAmount, unstakeAmount)
|
|
if stake.StakedAmount.Cmp(expectedRemaining) != 0 {
|
|
t.Fatalf("remaining stake mismatch: got %s, want %s", stake.StakedAmount, expectedRemaining)
|
|
}
|
|
}
|
|
|
|
func TestTransmuter_MultipleStakers(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
setBalance(stateDB, testUser2, bigInt("1000000000000000000000"))
|
|
setBalance(stateDB, transmuterAddr, bigInt("1000000000000000000000"))
|
|
|
|
// User1 stakes 100
|
|
user1Stake := bigInt("100000000000000000000")
|
|
transmuter.Stake(stateDB, testUser1, testLiquidToken, user1Stake)
|
|
|
|
// User2 stakes 200
|
|
user2Stake := bigInt("200000000000000000000")
|
|
transmuter.Stake(stateDB, testUser2, testLiquidToken, user2Stake)
|
|
|
|
// Deposit 150 underlying (should split proportionally)
|
|
depositAmount := bigInt("150000000000000000000")
|
|
transmuter.Deposit(stateDB, testLiquidToken, depositAmount)
|
|
|
|
// User1 should get 1/3 = 50
|
|
// User2 should get 2/3 = 100
|
|
claimable1 := transmuter.GetClaimable(stateDB, testUser1, testLiquidToken)
|
|
claimable2 := transmuter.GetClaimable(stateDB, testUser2, testLiquidToken)
|
|
|
|
expectedUser1 := bigInt("50000000000000000000")
|
|
expectedUser2 := bigInt("100000000000000000000")
|
|
|
|
if claimable1.Cmp(expectedUser1) != 0 {
|
|
t.Fatalf("user1 claimable mismatch: got %s, want %s", claimable1, expectedUser1)
|
|
}
|
|
if claimable2.Cmp(expectedUser2) != 0 {
|
|
t.Fatalf("user2 claimable mismatch: got %s, want %s", claimable2, expectedUser2)
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// Integration Tests
|
|
// =========================================================================
|
|
|
|
func TestLiquid_FullFlow(t *testing.T) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
// Setup tokens
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
|
|
// User deposits LP tokens
|
|
setBalance(stateDB, testUser1, bigInt("1000000000000000000000"))
|
|
depositAmount := bigInt("100000000000000000000")
|
|
alchemist.Deposit(stateDB, testUser1, testYieldToken, depositAmount)
|
|
|
|
// User mints liquid at 90% LTV
|
|
maxMint := alchemist.GetMaxMintable(stateDB, testUser1, testYieldToken)
|
|
alchemist.Mint(stateDB, testUser1, testYieldToken, testLiquidToken, maxMint)
|
|
|
|
// Verify position
|
|
account := alchemist.GetAccount(stateDB, testUser1, testYieldToken)
|
|
if account.Debt.Cmp(maxMint) != 0 {
|
|
t.Fatal("debt should equal minted amount")
|
|
}
|
|
|
|
ltv := alchemist.GetLTV(stateDB, testUser1, testYieldToken)
|
|
if ltv.Int64() != MaxLTV {
|
|
t.Fatalf("LTV should be 90%%, got %d", ltv.Int64())
|
|
}
|
|
|
|
// User can use liquidTokens (e.g., stake in transmuter)
|
|
transmuter.Stake(stateDB, testUser1, testLiquidToken, maxMint)
|
|
|
|
// Verify liquidTokens are staked
|
|
stake := transmuter.GetStake(stateDB, testUser1, testLiquidToken)
|
|
if stake.StakedAmount.Sign() == 0 {
|
|
t.Fatal("no liquidTokens staked")
|
|
}
|
|
|
|
t.Logf("Full flow completed:")
|
|
t.Logf(" Deposited: %s yield tokens", depositAmount)
|
|
t.Logf(" Minted: %s liquidTokens (90%% LTV)", maxMint)
|
|
t.Logf(" Staked in transmuter: %s", stake.StakedAmount)
|
|
}
|
|
|
|
// =========================================================================
|
|
// Benchmark Tests
|
|
// =========================================================================
|
|
|
|
func BenchmarkLiquid_Deposit(b *testing.B) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
depositAmount := bigInt("1000000000000000000")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
// Use different users to avoid harvest overhead
|
|
user := common.BigToAddress(big.NewInt(int64(i)))
|
|
setBalance(stateDB, user, bigInt("1000000000000000000000000000000"))
|
|
alchemist.Deposit(stateDB, user, testYieldToken, depositAmount)
|
|
}
|
|
}
|
|
|
|
func BenchmarkLiquid_Mint(b *testing.B) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
stateDB := NewMockStateDB()
|
|
|
|
yieldPerBlock := bigInt("1000000000000000")
|
|
debtCeiling := bigInt("1000000000000000000000000000000")
|
|
alchemist.AddYieldToken(stateDB, testYieldToken, testUnderlying, yieldPerBlock)
|
|
alchemist.AddLiquidToken(stateDB, testLiquidToken, testUnderlying, debtCeiling)
|
|
|
|
// Pre-deposit for many users
|
|
depositAmount := bigInt("100000000000000000000")
|
|
mintAmount := bigInt("50000000000000000000")
|
|
for i := 0; i < b.N; i++ {
|
|
user := common.BigToAddress(big.NewInt(int64(i)))
|
|
setBalance(stateDB, user, bigInt("1000000000000000000000000000000"))
|
|
alchemist.Deposit(stateDB, user, testYieldToken, depositAmount)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
user := common.BigToAddress(big.NewInt(int64(i)))
|
|
alchemist.Mint(stateDB, user, testYieldToken, testLiquidToken, mintAmount)
|
|
}
|
|
}
|
|
|
|
func BenchmarkTransmuter_Stake(b *testing.B) {
|
|
pm := NewPoolManager()
|
|
alchemist := NewLiquid(pm)
|
|
transmuter := NewTransmuter(alchemist)
|
|
stateDB := NewMockStateDB()
|
|
|
|
transmuter.InitializeTransmuter(stateDB, testLiquidToken, testUnderlying)
|
|
stakeAmount := bigInt("1000000000000000000")
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
user := common.BigToAddress(big.NewInt(int64(i)))
|
|
setBalance(stateDB, user, bigInt("1000000000000000000000000000000"))
|
|
transmuter.Stake(stateDB, user, testLiquidToken, stakeAmount)
|
|
}
|
|
}
|