From b3b3a860cb360700d62df9ca78838e699bf02735 Mon Sep 17 00:00:00 2001 From: Oliver Townsend Date: Tue, 21 Jan 2025 11:55:16 -0800 Subject: [PATCH] comments, some more tests --- .../plugins/ccip/internal/ccipcalc/calc.go | 28 +++--- .../ccip/internal/ccipcalc/calc_test.go | 90 ++++++++++++++++--- .../plugins/ccip/prices/da_price_estimator.go | 6 +- .../ccip/prices/exec_price_estimator.go | 8 +- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc.go b/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc.go index 7a82e2591c4..2a0e0244526 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc.go @@ -9,8 +9,9 @@ import ( ) const ( - // DESTINATION_ETH_PPB_GATE is the deviation threshold when writing to ethereum's PriceRegistry - EthereumThresholdGatePPB = 4e9 + // CurveBasedDeviationPPB is the deviation threshold when writing to ethereum's PriceRegistry and is the trigger for + // using curve-based deviation logic. + CurveBasedDeviationPPB = 4e9 ) // ContiguousReqs checks if seqNrs contains all numbers from min to max. @@ -70,13 +71,13 @@ func Deviates(x1, x2 *big.Int, ppb int64) bool { return diff.CmpAbs(big.NewInt(ppb)) > 0 // abs(diff) > ppb } -// DeviatesOnGasCurve calculates a deviation threshold on the fly using xNew. For now it's only used for gas price +// DeviatesOnCurve calculates a deviation threshold on the fly using xNew. For now it's only used for gas price // deviation calculation. It's important to make sure the order of xNew and xOld is correct when passed into this // function to get an accurate deviation threshold. -func DeviatesOnGasCurve(xNew, xOld, noDeviationLowerBound *big.Int, ppb int64) bool { +func DeviatesOnCurve(xNew, xOld, noDeviationLowerBound *big.Int, ppb int64) bool { // This is a temporary gating mechanism that ensures we only apply the gas curve deviation logic to eth-bound price // updates. If ppb from config is not equal to 4000000000, do not apply the gas curve. - if ppb != EthereumThresholdGatePPB { + if ppb != CurveBasedDeviationPPB { return Deviates(xOld, xNew, ppb) } @@ -88,19 +89,24 @@ func DeviatesOnGasCurve(xNew, xOld, noDeviationLowerBound *big.Int, ppb int64) b xNewFloat := new(big.Float).SetInt(xNew) xNewFloat64, _ := xNewFloat.Float64() - // Calculate the deviation threshold percentage with xNew using the formula: y = (10e11) / (xNew^0.665) // We use xNew to generate the threshold so that when going from cheap --> expensive, xNew generates a smaller // deviation threshold so we are more likely to update the gas price on chain. When going from expensive --> cheap, // xNew generates a larger deviation threshold since it's not as urgent to update the gas price on chain. + curveThresholdPPB := calculateCurveThresholdPPB(xNewFloat64) + return Deviates(xNew, xOld, curveThresholdPPB) +} + +// calculateCurveThresholdPPB calculates the deviation threshold percentage with xNew using the formula: +// y = (10e11) / (xNew^0.665). +func calculateCurveThresholdPPB(x float64) int64 { const constantFactor = 10e11 const exponent = 0.665 - xNewPower := math.Pow(xNewFloat64, exponent) - threshold := constantFactor / xNewPower + xPower := math.Pow(x, exponent) + threshold := constantFactor / xPower - // Convert percentage to PPB + // Convert curve output percentage to PPB thresholdPPB := int64(threshold * 1e7) - - return Deviates(xNew, xOld, thresholdPPB) + return thresholdPPB } func MergeEpochAndRound(epoch uint32, round uint8) uint64 { diff --git a/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc_test.go b/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc_test.go index 7fa0cb4ebb5..55c2ccbf027 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc_test.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipcalc/calc_test.go @@ -1,6 +1,7 @@ package ccipcalc import ( + "fmt" "math" "math/big" "testing" @@ -233,37 +234,37 @@ func TestDeviatesOnGasCurve(t *testing.T) { }{ { name: "base case deviates from increase", - args: args{xNew: big.NewInt(4e14), xOld: big.NewInt(1e13), noDev: big.NewInt(3e13), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(4e14), xOld: big.NewInt(1e13), noDev: big.NewInt(3e13), ppb: CurveBasedDeviationPPB}, want: true, }, { name: "base case deviates from decrease", - args: args{xNew: big.NewInt(1e13), xOld: big.NewInt(4e15), noDev: big.NewInt(1), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(1e13), xOld: big.NewInt(4e15), noDev: big.NewInt(1), ppb: CurveBasedDeviationPPB}, want: true, }, { name: "does not deviate when equal", - args: args{xNew: big.NewInt(3e14), xOld: big.NewInt(3e14), noDev: big.NewInt(3e13), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(3e14), xOld: big.NewInt(3e14), noDev: big.NewInt(3e13), ppb: CurveBasedDeviationPPB}, want: false, }, { name: "does not deviate with small difference when xNew is bigger", - args: args{xNew: big.NewInt(3e14 + 1), xOld: big.NewInt(3e14), noDev: big.NewInt(3e13), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(3e14 + 1), xOld: big.NewInt(3e14), noDev: big.NewInt(3e13), ppb: CurveBasedDeviationPPB}, want: false, }, { name: "does not deviate with small difference when xOld is bigger", - args: args{xNew: big.NewInt(3e14), xOld: big.NewInt(3e14 + 1), noDev: big.NewInt(3e13), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(3e14), xOld: big.NewInt(3e14 + 1), noDev: big.NewInt(3e13), ppb: CurveBasedDeviationPPB}, want: false, }, { - name: "deviates when ppb is not equal to EthereumThresholdGatePPB", + name: "deviates when ppb is not equal to CurveBasedDeviationPPB", args: args{xNew: big.NewInt(1e9), xOld: big.NewInt(2e9), noDev: big.NewInt(1), ppb: 1}, want: true, }, { name: "does not deviate when xNew is below noDeviationLowerBound", - args: args{xNew: big.NewInt(2e13), xOld: big.NewInt(1e13), noDev: big.NewInt(3e13), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(2e13), xOld: big.NewInt(1e13), noDev: big.NewInt(3e13), ppb: CurveBasedDeviationPPB}, want: false, }, // thresholdPPB = (10e11) / (xNew^0.665) * 1e7 @@ -271,12 +272,12 @@ func TestDeviatesOnGasCurve(t *testing.T) { // Deviates = abs(diff) > thresholdPPB { name: "xNew is just below deviation threshold and does deviate", - args: args{xNew: big.NewInt(3e13), xOld: big.NewInt(2.519478222838e12), noDev: big.NewInt(1), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(3e13), xOld: big.NewInt(2.519478222838e12), noDev: big.NewInt(1), ppb: CurveBasedDeviationPPB}, want: false, }, { name: "xNew is just above deviation threshold and does deviate", - args: args{xNew: big.NewInt(3e13), xOld: big.NewInt(2.519478222838e12 - 30), noDev: big.NewInt(1), ppb: EthereumThresholdGatePPB}, + args: args{xNew: big.NewInt(3e13), xOld: big.NewInt(2.519478222838e12 - 30), noDev: big.NewInt(1), ppb: CurveBasedDeviationPPB}, want: true, }, } @@ -285,8 +286,75 @@ func TestDeviatesOnGasCurve(t *testing.T) { assert.Equalf( t, tt.want, - DeviatesOnGasCurve(tt.args.xNew, tt.args.xOld, tt.args.noDev, tt.args.ppb), - "DeviatesOnGasCurve(%v, %v, %v, %v)", tt.args.xNew, tt.args.xOld, tt.args.noDev, tt.args.ppb) + DeviatesOnCurve(tt.args.xNew, tt.args.xOld, tt.args.noDev, tt.args.ppb), + "DeviatesOnCurve(%v, %v, %v, %v)", tt.args.xNew, tt.args.xOld, tt.args.noDev, tt.args.ppb) + }) + } +} + +func TestCalculateCurveThresholdPPB(t *testing.T) { + tests := []struct { + x float64 + ppbLowerBound int64 + ppbUpperBound int64 + }{ + { + x: 500_000, + ppbLowerBound: 1_000_000_000_000_000, // 100,000,000% + ppbUpperBound: 3_000_000_000_000_000, // 300,000,000% + }, + { + x: 50_000_000, + ppbLowerBound: 70_000_000_000_000, // 7,000,000% + ppbUpperBound: 90_000_000_000_000, // 9,000,000% + }, + { + x: 350_000_000, + ppbLowerBound: 20_000_000_000_000, // 2,000,000% + ppbUpperBound: 30_000_000_000_000, // 3,000,000% + }, + { + x: 200_000_000_000, + ppbLowerBound: 300_000_000_000, // 30,000% + ppbUpperBound: 400_000_000_000, // 40,000% + }, + { + x: 6_000_000_000_000, + ppbLowerBound: 30_000_000_000, // 3,000% + ppbUpperBound: 40000000000, // 4,000% + }, + { + x: 50_000_000_000_000, + ppbLowerBound: 7_000_000_000, // 700% + ppbUpperBound: 8_000_000_000, // 800% + }, + { + x: 225_000_000_000_000, + ppbLowerBound: 2_000_000_000, // 200% + ppbUpperBound: 3_000_000_000, // 300% + }, + { + x: 5_000_000_000_000_000, + ppbLowerBound: 300_000_000, // 30% + ppbUpperBound: 400_000_000, // 40% + }, + { + x: 70_000_000_000_000_000, + ppbLowerBound: 60_000_000, // 6% + ppbUpperBound: 70_000_000, // 7% + }, + { + x: 500_000_000_000_000_000, + ppbLowerBound: 10_000_000, // 1% + ppbUpperBound: 20_000_000, // 2% + }, + } + for _, tt := range tests { + // convert tt.x to string + t.Run(fmt.Sprintf("%f", tt.x), func(t *testing.T) { + thresholdPPB := calculateCurveThresholdPPB(tt.x) + assert.GreaterOrEqual(t, thresholdPPB, tt.ppbLowerBound) + assert.LessOrEqual(t, thresholdPPB, tt.ppbUpperBound) }) } } diff --git a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go index 0336a4ec849..61c5a44152d 100644 --- a/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/da_price_estimator.go @@ -15,10 +15,6 @@ import ( ) const ( - // ExecNoDeviationThresholdUSD is the lower bound no deviation threshold for exec gas. If the exec gas price is - // less than this value, we should never trigger a deviation. This is set to 10 gwei in USD terms. - ExecNoDeviationThresholdUSD = 10e9 - // DANoDeviationThresholdUSD is the lower bound no deviation threshold for DA gas. If the DA gas price is less // than this value, we should never trigger a deviation. This is set to 20 gwei in USD terms. DANoDeviationThresholdUSD = 20e9 @@ -145,7 +141,7 @@ func (g DAGasPriceEstimator) Deviates(ctx context.Context, p1, p2 *big.Int) (boo return execDeviates, nil } - return ccipcalc.DeviatesOnGasCurve(p1DAGasPrice, p2DAGasPrice, big.NewInt(DANoDeviationThresholdUSD), g.daDeviationPPB), nil + return ccipcalc.DeviatesOnCurve(p1DAGasPrice, p2DAGasPrice, big.NewInt(DANoDeviationThresholdUSD), g.daDeviationPPB), nil } func (g DAGasPriceEstimator) EstimateMsgCostUSD(ctx context.Context, p *big.Int, wrappedNativePrice *big.Int, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta) (*big.Int, error) { diff --git a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go index a7ea4d50688..e976927db01 100644 --- a/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go +++ b/core/services/ocr2/plugins/ccip/prices/exec_price_estimator.go @@ -12,6 +12,12 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" ) +const ( + // ExecNoDeviationThresholdUSD is the lower bound no deviation threshold for exec gas. If the exec gas price is + // less than this value, we should never trigger a deviation. This is set to 10 gwei in USD terms. + ExecNoDeviationThresholdUSD = 10e9 +) + type ExecGasPriceEstimator struct { estimator gas.EvmFeeEstimator maxGasPrice *big.Int @@ -52,7 +58,7 @@ func (g ExecGasPriceEstimator) Median(ctx context.Context, gasPrices []*big.Int) } func (g ExecGasPriceEstimator) Deviates(ctx context.Context, p1 *big.Int, p2 *big.Int) (bool, error) { - return ccipcalc.DeviatesOnGasCurve(p1, p2, big.NewInt(ExecNoDeviationThresholdUSD), g.deviationPPB), nil + return ccipcalc.DeviatesOnCurve(p1, p2, big.NewInt(ExecNoDeviationThresholdUSD), g.deviationPPB), nil } func (g ExecGasPriceEstimator) EstimateMsgCostUSD(ctx context.Context, p *big.Int, wrappedNativePrice *big.Int, msg cciptypes.EVM2EVMOnRampCCIPSendRequestedWithMeta) (*big.Int, error) {