Skip to content

Commit 3f68c1d

Browse files
authored
ft/bal v3 surge risk check (#1246)
* Revert "ft: inflate StableSurge imbalance to account for onchain slippage (#1101)" This reverts commit e3dbeea. * ft: update bal-v3 stable surge risk check
1 parent 3f3b27a commit 3f68c1d

File tree

9 files changed

+86
-61
lines changed

9 files changed

+86
-61
lines changed

pkg/liquidity-source/balancer/v3/hooks/stable_surge.go

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package hooks
22

33
import (
4-
"math"
5-
"math/big"
64
"slices"
75

86
"github.com/holiman/uint256"
97

10-
bmath "github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/balancer/v3/math"
8+
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/balancer/v3/math"
119
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/balancer/v3/shared"
1210
)
1311

@@ -38,15 +36,8 @@ func (h *StableSurgeHook) getSurgeFeePercentage(params shared.PoolSwapParams) *u
3836
newBalances := slices.Clone(params.BalancesScaled18)
3937

4038
if params.Kind == shared.ExactIn {
41-
// inflate imbalance to account for on-chain slippage
42-
balOutF := newBalances[params.IndexOut].Float64()
43-
amtOutF := balOutF * math.Log(3.2+32*amtCalculatedScaled18.Float64()/balOutF) / math.Log(3.2+32)
44-
amtOutB, _ := big.NewFloat(amtOutF).Int(nil)
45-
var inflatedAmt uint256.Int
46-
inflatedAmt.SetFromBig(amtOutB)
47-
newBalances[params.IndexOut] = new(uint256.Int).Sub(newBalances[params.IndexOut], &inflatedAmt)
48-
inflatedAmt.MulDivOverflow(params.AmountGivenScaled18, &inflatedAmt, amtCalculatedScaled18)
49-
newBalances[params.IndexIn] = inflatedAmt.Add(newBalances[params.IndexIn], &inflatedAmt)
39+
newBalances[params.IndexIn] = new(uint256.Int).Add(newBalances[params.IndexIn], params.AmountGivenScaled18)
40+
newBalances[params.IndexOut] = amtCalculatedScaled18.Sub(newBalances[params.IndexOut], amtCalculatedScaled18)
5041
} else {
5142
newBalances[params.IndexIn] = amtCalculatedScaled18.Add(newBalances[params.IndexIn], amtCalculatedScaled18)
5243
newBalances[params.IndexOut] = new(uint256.Int).Sub(newBalances[params.IndexOut], params.AmountGivenScaled18)
@@ -60,17 +51,17 @@ func (h *StableSurgeHook) _getSurgeFeePercentage(params shared.PoolSwapParams, b
6051
return params.StaticSwapFeePercentage
6152
}
6253

63-
newTotalImbalance, err := bmath.StableSurgeMedian.CalculateImbalance(balances)
54+
newTotalImbalance, err := math.StableSurgeMedian.CalculateImbalance(balances)
6455
if err != nil || !h._isSurging(params.BalancesScaled18, newTotalImbalance) {
6556
return params.StaticSwapFeePercentage
6657
}
6758

68-
tmp, err := bmath.FixPoint.DivDown(newTotalImbalance.Sub(newTotalImbalance, h.ThresholdPercentage),
69-
bmath.FixPoint.Complement(h.ThresholdPercentage))
59+
tmp, err := math.FixPoint.DivDown(newTotalImbalance.Sub(newTotalImbalance, h.ThresholdPercentage),
60+
math.FixPoint.Complement(h.ThresholdPercentage))
7061
if err != nil {
7162
return params.StaticSwapFeePercentage
7263
}
73-
tmp, err = bmath.FixPoint.MulDown(newTotalImbalance.Sub(h.MaxSurgeFeePercentage, params.StaticSwapFeePercentage),
64+
tmp, err = math.FixPoint.MulDown(newTotalImbalance.Sub(h.MaxSurgeFeePercentage, params.StaticSwapFeePercentage),
7465
tmp)
7566
if err != nil {
7667
return params.StaticSwapFeePercentage
@@ -82,6 +73,6 @@ func (h *StableSurgeHook) _isSurging(currentBalances []*uint256.Int, newTotalImb
8273
if newTotalImbalance.IsZero() || !newTotalImbalance.Gt(h.ThresholdPercentage) {
8374
return false
8475
}
85-
oldTotalImbalance, err := bmath.StableSurgeMedian.CalculateImbalance(currentBalances)
76+
oldTotalImbalance, err := math.StableSurgeMedian.CalculateImbalance(currentBalances)
8677
return err == nil && newTotalImbalance.Gt(oldTotalImbalance)
8778
}

pkg/liquidity-source/balancer/v3/shared/config.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ package shared
22

33
import (
44
"net/http"
5+
6+
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject"
57
)
68

79
type Config struct {
8-
DexID string `json:"dexID,omitempty"`
9-
ChainID int `json:"chainID,omitempty"`
10-
PoolType string `json:"poolType,omitempty"`
11-
SubgraphAPI string `json:"subgraphAPI,omitempty"`
12-
SubgraphHeaders http.Header `json:"subgraphHeaders,omitempty"`
13-
NewPoolLimit int `json:"newPoolLimit,omitempty"`
14-
VaultExplorer string `json:"vaultExplorer"`
15-
SubgraphChain string `json:"subgraphChain"`
16-
SubgraphPoolType string `json:"-"`
10+
DexID string `json:"dexID,omitempty"`
11+
ChainID valueobject.ChainID `json:"chainID,omitempty"`
12+
PoolType string `json:"poolType,omitempty"`
13+
SubgraphAPI string `json:"subgraphAPI,omitempty"`
14+
SubgraphHeaders http.Header `json:"subgraphHeaders,omitempty"`
15+
NewPoolLimit int `json:"newPoolLimit,omitempty"`
16+
VaultExplorer string `json:"vaultExplorer"`
17+
SubgraphChain string `json:"subgraphChain"`
18+
SubgraphPoolType string `json:"-"`
1719
}

pkg/liquidity-source/balancer/v3/shared/pool_tracker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ import (
1111
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject"
1212
)
1313

14-
func GetBufferTokens(req *ethrpc.Request, chainID int, exchange string, bufferTokens []string) func() []*ExtraBuffer {
14+
func GetBufferTokens(req *ethrpc.Request, chainID valueobject.ChainID, exchange string, bufferTokens []string) func() []*ExtraBuffer {
1515
var (
1616
rates = make([][]Rate, len(bufferTokens))
1717
maxDeposits = make([]*big.Int, len(bufferTokens))
1818
maxRedeems = make([]*big.Int, len(bufferTokens))
1919
)
2020

21-
vault := Vault(valueobject.ChainID(chainID), exchange)
21+
vault := Vault(chainID, exchange)
2222
for i, bufferToken := range bufferTokens {
2323
if bufferToken == "" {
2424
continue

pkg/liquidity-source/balancer/v3/stable/constant.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package stable
22

3+
import (
4+
"github.com/holiman/uint256"
5+
6+
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject"
7+
)
8+
39
const (
410
DexType = "balancer-v3-stable"
511

@@ -12,3 +18,19 @@ const (
1218

1319
baseGas = 237494
1420
)
21+
22+
var (
23+
// AcceptableMaxSurgeFeePercentage caps max acceptable surge fee to avoid high slippage
24+
AcceptableMaxSurgeFeePercentage = uint256.NewInt(0.1e18) // 10%
25+
// AcceptableMaxSurgeFeeByImbalance caps max acceptable surge fee per imbalance to avoid high slippage
26+
AcceptableMaxSurgeFeeByImbalance = uint256.NewInt(0.1e18) // 0.1% per 1% of imbalance
27+
28+
stablesByChain = map[valueobject.ChainID]map[string]bool{
29+
valueobject.ChainIDBase: {
30+
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": true,
31+
},
32+
valueobject.ChainIDSonic: {
33+
"0x29219dd400f2bf60e5a23d13be72b486d4038894": true,
34+
},
35+
}
36+
)

pkg/liquidity-source/balancer/v3/stable/pool_simulator.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ func NewPoolSimulator(params pool.FactoryParams) (*base.PoolSimulator, error) {
2929
var hook hooks.IHook
3030
switch staticExtra.HookType {
3131
case shared.StableSurgeHookType:
32+
if extra.isRisky(entityPool, params.ChainID) {
33+
return nil, shared.ErrUnsupportedHook
34+
}
3235
hook = hooks.NewStableSurgeHook(extra.MaxSurgeFeePercentage, extra.SurgeThresholdPercentage)
3336
}
3437

pkg/liquidity-source/balancer/v3/stable/pool_simulator_test.go

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,10 @@ import (
1616
)
1717

1818
var (
19-
entityPool, eP2 entity.Pool
20-
_ = json.Unmarshal([]byte(`{"address":"0xc4ce391d82d164c166df9c8336ddf84206b2f812","exchange":"balancer-v3-stable","type":"balancer-v3-stable","timestamp":1751293016,"reserves":["687804073931103275644","1783969556654743519024"],"tokens":[{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},{"address":"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0"}],"extra":"{\"hook\":{},\"fee\":\"20000000000000\",\"aggrFee\":\"500000000000000000\",\"balsE18\":[\"694069210892948295209\",\"2124492373418339554414\"],\"decs\":[\"1\",\"1\"],\"rates\":[\"1009108897721464489\",\"1190879275654308905\"],\"buffs\":[{\"dRate\":[\"976255\",\"976255817341\",\"976255817341645373\",\"976255817341645373456045\",\"976255817341645373456045753577\"],\"rRate\":[\"1024321\",\"1024321681096\",\"1024321681096877127\",\"1024321681096877127977750\",\"1024321681096877127977750950000\"]},{\"dRate\":[\"996629\",\"996629442697\",\"996629442697471179\",\"996629442697471179789157\",\"996629442697471179789157582365\"],\"rRate\":[\"1003381\",\"1003381956380\",\"1003381956380303285\",\"1003381956380303285385258\",\"1003381956380303285385258382000\"]}],\"surge\":{},\"ampParam\":\"5000000\"}","staticExtra":"{\"buffs\":[\"0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9\",\"0x775f661b0bd1739349b9a2a3ef60be277c5d2d29\"]}","blockNumber":22817774}`),
19+
entityPool entity.Pool
20+
_ = json.Unmarshal([]byte(`{"address":"0xc4ce391d82d164c166df9c8336ddf84206b2f812","exchange":"balancer-v3-stable","type":"balancer-v3-stable","timestamp":1751293016,"reserves":["687804073931103275644","1783969556654743519024"],"tokens":[{"address":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"},{"address":"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0"}],"extra":"{\"hook\":{},\"fee\":\"20000000000000\",\"aggrFee\":\"500000000000000000\",\"balsE18\":[\"694069210892948295209\",\"2124492373418339554414\"],\"decs\":[\"1\",\"1\"],\"rates\":[\"1009108897721464489\",\"1190879275654308905\"],\"buffs\":[{\"dRate\":[\"976255\",\"976255817341\",\"976255817341645373\",\"976255817341645373456045\",\"976255817341645373456045753577\"],\"rRate\":[\"1024321\",\"1024321681096\",\"1024321681096877127\",\"1024321681096877127977750\",\"1024321681096877127977750950000\"]},{\"dRate\":[\"996629\",\"996629442697\",\"996629442697471179\",\"996629442697471179789157\",\"996629442697471179789157582365\"],\"rRate\":[\"1003381\",\"1003381956380\",\"1003381956380303285\",\"1003381956380303285385258\",\"1003381956380303285385258382000\"]}],\"surge\":{},\"ampParam\":\"5000000\"}","staticExtra":"{\"buffs\":[\"0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9\",\"0x775f661b0bd1739349b9a2a3ef60be277c5d2d29\"]}","blockNumber":22817774}`),
2121
&entityPool)
22-
_ = json.Unmarshal([]byte(`{"address":"0x79f96e2160c9069bd819161276320d8c8beab0ea","exchange":"balancer-v3-stable","type":"balancer-v3-stable","timestamp":1759834811,"reserves":["145112515681572525","157550194","487789","133457591"],"tokens":[{"address":"0x4200000000000000000000000000000000000006","symbol":"WETH","decimals":18,"swappable":true},{"address":"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","symbol":"USDC","decimals":6,"swappable":true},{"address":"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","symbol":"cbBTC","decimals":8,"swappable":true},{"address":"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2","symbol":"USDT","decimals":6,"swappable":true},{"address":"0xc768c589647798a6ee01a91fde98ef2ed046dbd6","symbol":"waBasUSDC","decimals":6,"swappable":true}],"extra":"{\"hook\":{\"dynFee\":true},\"fee\":\"8000000000000\",\"aggrFee\":\"500000000000000000\",\"balsE18\":[\"681593916124730114424\",\"174733118599984050167\",\"606843756465924360000\",\"133457591000000000000\"],\"decs\":[\"1\",\"1000000000000\",\"10000000000\",\"1000000000000\"],\"rates\":[\"4697002963000000000000\",\"1109063176399414971\",\"124407019524000000000000\",\"1000000000000000000\"],\"buffs\":[null,{\"dRate\":[\"901661\",\"901661890214\",\"901661890214866120\",\"901661890214866120568244\",\"901661890214866120568244980649\"],\"rRate\":[\"1109063\",\"1109063176399\",\"1109063176399414971\",\"1109063176399414971194981\",\"1109063176399414971194981471000\"]},null,null],\"surge\":{\"max\":\"100000000000000000\",\"thres\":\"300000000000000000\"},\"ampParam\":\"5000000\"}","staticExtra":"{\"hook\":\"0xdb8d758bcb971e482b2c45f7f8a7740283a1bd3a\",\"hookT\":\"STABLE_SURGE\",\"buffs\":[\"\",\"0xc768c589647798a6ee01a91fde98ef2ed046dbd6\",\"\",\"\"]}","blockNumber":36522732}`),
23-
&eP2)
2422
poolSim = lo.Must(NewPoolSimulator(pool.FactoryParams{EntityPool: entityPool}))
25-
pS2 = lo.Must(NewPoolSimulator(pool.FactoryParams{EntityPool: eP2}))
2623
)
2724

2825
func TestCalcAmountOut(t *testing.T) {
@@ -111,31 +108,8 @@ func TestCalcAmountOut(t *testing.T) {
111108
})
112109
}
113110

114-
func TestCalcAmountOutSurge(t *testing.T) {
115-
t.Parallel()
116-
testutil.TestCalcAmountOut(t, pS2, map[int]map[int]map[string]string{
117-
1: {
118-
2: {
119-
"100000000": "80420", // "80420",
120-
"200000000": "153185", // "160812",
121-
"400000000": "300714", // "321540",
122-
"800000000": "487638", // "487638",
123-
},
124-
},
125-
2: {
126-
1: {
127-
"40000": "46378456", // "47031432",
128-
"80000": "92208588", // "93112601",
129-
"160000": "172329018", // "172333931",
130-
"320000": "174614087", // "174614090",
131-
},
132-
},
133-
})
134-
}
135-
136111
func TestCalcAmountIn(t *testing.T) {
137112
testutil.TestCalcAmountIn(t, poolSim, 8)
138-
testutil.TestCalcAmountIn(t, pS2, 4)
139113
}
140114

141115
func TestCanSwapTo(t *testing.T) {

pkg/liquidity-source/balancer/v3/stable/pool_tracker.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import (
1515
"github.com/samber/lo"
1616

1717
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/entity"
18+
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/balancer/v3/math"
1819
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/liquidity-source/balancer/v3/shared"
1920
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool"
2021
pooltrack "github.com/KyberNetwork/kyberswap-dex-lib/pkg/source/pool/tracker"
22+
"github.com/KyberNetwork/kyberswap-dex-lib/pkg/valueobject"
2123
)
2224

2325
type PoolTracker struct {
@@ -95,6 +97,7 @@ func (t *PoolTracker) getNewPoolState(
9597
if staticExtra.HookType == shared.StableSurgeHookType {
9698
extra.MaxSurgeFeePercentage, _ = uint256.FromBig(res.MaxSurgeFeePercentage)
9799
extra.SurgeThresholdPercentage, _ = uint256.FromBig(res.SurgeThresholdPercentage)
100+
extra.IsRisky = extra.isRisky(p, t.config.ChainID)
98101
}
99102
extra.AmplificationParameter, _ = uint256.FromBig(res.Value)
100103

@@ -108,7 +111,7 @@ func (t *PoolTracker) getNewPoolState(
108111
p.Extra = string(extraBytes)
109112
p.Timestamp = time.Now().Unix()
110113

111-
if res.IsPoolDisabled || !shared.IsHookSupported(staticExtra.HookType) {
114+
if res.IsPoolDisabled || extra.IsRisky || !shared.IsHookSupported(staticExtra.HookType) {
112115
// set all reserves to 0 to disable pool
113116
p.Reserves = lo.Map(p.Reserves, func(_ string, _ int) string { return "0" })
114117
} else {
@@ -195,3 +198,28 @@ func (t *PoolTracker) queryRPCData(ctx context.Context, p *entity.Pool, staticEx
195198

196199
return &rpcRes, nil
197200
}
201+
202+
func (s SurgePercentages) isRisky(p entity.Pool, chainId valueobject.ChainID) bool {
203+
if s.MaxSurgeFeePercentage == nil || s.SurgeThresholdPercentage == nil ||
204+
s.MaxSurgeFeePercentage.Cmp(AcceptableMaxSurgeFeePercentage) <= 0 &&
205+
math.StableSurgeMedian.CalculateFeeSurgeRatio(s.MaxSurgeFeePercentage, s.SurgeThresholdPercentage).
206+
Cmp(AcceptableMaxSurgeFeeByImbalance) <= 0 {
207+
return false
208+
}
209+
210+
var hasNative, hasStable bool
211+
for _, token := range p.Tokens {
212+
if !hasNative && valueobject.IsWrappedNative(token.Address, chainId) {
213+
if hasStable {
214+
return true
215+
}
216+
hasNative = true
217+
} else if !hasStable && stablesByChain[chainId][token.Address] {
218+
if hasNative {
219+
return true
220+
}
221+
hasStable = true
222+
}
223+
}
224+
return false
225+
}

pkg/liquidity-source/balancer/v3/stable/type.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Extra struct {
1717
type SurgePercentages struct {
1818
MaxSurgeFeePercentage *uint256.Int `json:"max,omitempty"`
1919
SurgeThresholdPercentage *uint256.Int `json:"thres,omitempty"`
20+
IsRisky bool `json:"r,omitempty"`
2021
}
2122

2223
type RpcResult struct {

pkg/valueobject/wrapped_native.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,29 @@ var WrappedNativeMap = map[ChainID]string{
5050
ChainIDMegaETH: "0x4200000000000000000000000000000000000006",
5151
}
5252

53+
// LowerWrapped returns lowercase wrapped token string
54+
func LowerWrapped(chainID ChainID) string {
55+
return strings.ToLower(WrappedNativeMap[chainID])
56+
}
57+
5358
// WrapNativeLower wraps, if applicable, native token to wrapped token; and then lowercase it.
5459
func WrapNativeLower(token string, chainID ChainID) string {
5560
if IsNative(token) {
56-
token = WrappedNativeMap[chainID]
61+
return LowerWrapped(chainID)
5762
}
5863
return strings.ToLower(token)
5964
}
6065

6166
func ZeroToWrappedLower(address string, chainID ChainID) string {
6267
if IsZero(address) {
63-
return strings.ToLower(WrappedNativeMap[chainID])
68+
return LowerWrapped(chainID)
6469
}
65-
6670
return strings.ToLower(address)
6771
}
6872

6973
func WrapNativeZeroLower(token string, chainID ChainID) string {
7074
if IsNativeOrZero(token) {
71-
token = WrappedNativeMap[chainID]
75+
return LowerWrapped(chainID)
7276
}
7377
return strings.ToLower(token)
7478
}

0 commit comments

Comments
 (0)