Skip to content

Commit

Permalink
Revert the revert on fee boosting + using mathlibs (#309)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xnogo authored Nov 15, 2024
1 parent 82c7f2c commit ec39846
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 41 deletions.
58 changes: 50 additions & 8 deletions execute/exectypes/costly_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
"time"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
"github.com/smartcontractkit/chainlink-common/pkg/types"

"github.com/smartcontractkit/chainlink-ccip/execute/internal/gas"

"github.com/smartcontractkit/chainlink-ccip/internal/libs/mathslib"
"github.com/smartcontractkit/chainlink-ccip/internal/plugintypes"
readerpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader"
cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3"
Expand Down Expand Up @@ -118,6 +119,8 @@ func (o *CCIPCostlyMessageObserver) Observe(
return nil, fmt.Errorf("missing exec cost for message %s", msg.Header.MessageID)
}
if fee.Cmp(execCost) < 0 {
o.lggr.Warnw("Message is too costly to execute", "messageID",
msg.Header.MessageID.String(), "fee", fee, "execCost", execCost, "seqNum", msg.Header.SequenceNumber)
costlyMessages = append(costlyMessages, msg.Header.MessageID)
}
}
Expand Down Expand Up @@ -297,7 +300,10 @@ func (c *CCIPMessageFeeUSD18Calculator) MessageFeeUSD18(

messageFees := make(map[cciptypes.Bytes32]plugintypes.USD18)
for _, msg := range messages {
feeUSD18 := new(big.Int).Mul(linkPriceUSD.Int, msg.FeeValueJuels.Int)
feeUSD18 := new(big.Int).Div(
new(big.Int).Mul(linkPriceUSD.Int, msg.FeeValueJuels.Int),
new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil),
)
timestamp, ok := messageTimeStamps[msg.Header.MessageID]
if !ok {
// If a timestamp is missing we can't do fee boosting, but we still record the fee. In the worst case, the
Expand All @@ -319,7 +325,10 @@ func (c *CCIPMessageFeeUSD18Calculator) MessageFeeUSD18(
// At the same time, messages that are slightly underpaid will start going through after waiting for a little bit.
//
// wait_boosted_fee(m) = (1 + (now - m.send_time).hours * RELATIVE_BOOST_PER_WAIT_HOUR) * fee(m)
func waitBoostedFee(waitTime time.Duration, fee *big.Int, relativeBoostPerWaitHour float64) *big.Int {
func waitBoostedFee(
waitTime time.Duration,
fee *big.Int,
relativeBoostPerWaitHour float64) *big.Int {
k := 1.0 + waitTime.Hours()*relativeBoostPerWaitHour

boostedFee := big.NewFloat(0).Mul(big.NewFloat(k), new(big.Float).SetInt(fee))
Expand Down Expand Up @@ -350,21 +359,55 @@ func (c *CCIPMessageExecCostUSD18Calculator) MessageExecCostUSD18(
if feeComponents.DataAvailabilityFee == nil {
return nil, fmt.Errorf("missing data availability fee")
}

executionFee, daFee, err := c.getFeesUSD18(ctx, feeComponents, messages[0].Header.DestChainSelector)
if err != nil {
return nil, fmt.Errorf("unable to convert fee components to USD18: %w", err)
}

daConfig, err := c.ccipReader.GetMedianDataAvailabilityGasConfig(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get data availability gas config: %w", err)
}

for _, msg := range messages {
executionCostUSD18 := c.computeExecutionCostUSD18(feeComponents.ExecutionFee, msg)
dataAvailabilityCostUSD18 := computeDataAvailabilityCostUSD18(feeComponents.DataAvailabilityFee, daConfig, msg)
executionCostUSD18 := c.computeExecutionCostUSD18(executionFee, msg)
dataAvailabilityCostUSD18 := c.computeDataAvailabilityCostUSD18(daFee, daConfig, msg)
totalCostUSD18 := new(big.Int).Add(executionCostUSD18, dataAvailabilityCostUSD18)
messageExecCosts[msg.Header.MessageID] = totalCostUSD18
}

return messageExecCosts, nil
}

func (c *CCIPMessageExecCostUSD18Calculator) getFeesUSD18(
ctx context.Context,
feeComponents types.ChainFeeComponents,
destChainSelector cciptypes.ChainSelector,
) (plugintypes.USD18, plugintypes.USD18, error) {
nativeTokenPrices := c.ccipReader.GetWrappedNativeTokenPriceUSD(
ctx,
[]cciptypes.ChainSelector{destChainSelector})
if nativeTokenPrices == nil {
return nil, nil, fmt.Errorf("unable to get native token prices")
}
nativeTokenPrice, ok := nativeTokenPrices[destChainSelector]
if !ok {
return nil, nil, fmt.Errorf("missing native token price for chain %s", destChainSelector)
}

executionFee := mathslib.CalculateUsdPerUnitGas(feeComponents.ExecutionFee, nativeTokenPrice.Int)
dataAvailabilityFee := mathslib.CalculateUsdPerUnitGas(feeComponents.DataAvailabilityFee, nativeTokenPrice.Int)

c.lggr.Debugw("Fee calculation", "nativeTokenPrice", nativeTokenPrice,
"feeComponents.ExecutionFee", feeComponents.ExecutionFee,
"feeComponents.DataAvailabilityFee", feeComponents.DataAvailabilityFee,
"executionFee", executionFee,
"dataAvailabilityFee", dataAvailabilityFee)

return executionFee, dataAvailabilityFee, nil
}

// computeExecutionCostUSD18 computes the execution cost of a message in USD18s.
// The cost is:
// messageGas (gas) * executionFee (USD18/gas) = USD18
Expand All @@ -373,12 +416,11 @@ func (c *CCIPMessageExecCostUSD18Calculator) computeExecutionCostUSD18(
message cciptypes.Message,
) plugintypes.USD18 {
messageGas := new(big.Int).SetUint64(c.estimateProvider.CalculateMessageMaxGas(message))
cost := new(big.Int).Mul(messageGas, executionFee)
return cost
return new(big.Int).Mul(messageGas, executionFee)
}

// computeDataAvailabilityCostUSD18 computes the data availability cost of a message in USD18s.
func computeDataAvailabilityCostUSD18(
func (c *CCIPMessageExecCostUSD18Calculator) computeDataAvailabilityCostUSD18(
dataAvailabilityFee *big.Int,
daConfig cciptypes.DataAvailabilityGasConfig,
message cciptypes.Message,
Expand Down
75 changes: 43 additions & 32 deletions execute/exectypes/costly_messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ func TestWaitBoostedFee(t *testing.T) {
0.7,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
boosted := waitBoostedFee(tc.sendTimeDiff, tc.fee, tc.relativeBoostPerWaitHour)
Expand Down Expand Up @@ -291,6 +290,10 @@ func TestCCIPMessageFeeE18USDCalculator_MessageFeeE18USD(t *testing.T) {
}

func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
destChainSelector := ccipocr3.ChainSelector(1)
nativeTokenPrice := ccipocr3.BigInt{
Int: new(big.Int).Mul(big.NewInt(9), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))}

tests := []struct {
name string
messages []ccipocr3.Message
Expand All @@ -306,17 +309,17 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
name: "happy path, no DA cost",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
},
{
Header: ccipocr3.RampMessageHeader{MessageID: b2},
Header: ccipocr3.RampMessageHeader{MessageID: b2, DestChainSelector: destChainSelector},
},
{
Header: ccipocr3.RampMessageHeader{MessageID: b3},
Header: ccipocr3.RampMessageHeader{MessageID: b3, DestChainSelector: destChainSelector},
},
},
messageGases: []uint64{100, 200, 300},
executionFee: big.NewInt(100),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: big.NewInt(0),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
Expand All @@ -325,46 +328,46 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
DestDataAvailabilityMultiplierBps: 1,
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(10000),
b2: plugintypes.NewUSD18(20000),
b3: plugintypes.NewUSD18(30000),
b1: plugintypes.NewUSD18(18000000000000),
b2: plugintypes.NewUSD18(36000000000000),
b3: plugintypes.NewUSD18(54000000000000),
},
wantErr: false,
},
{
name: "happy path, with DA cost",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
},
{
Header: ccipocr3.RampMessageHeader{MessageID: b2},
Header: ccipocr3.RampMessageHeader{MessageID: b2, DestChainSelector: destChainSelector},
},
{
Header: ccipocr3.RampMessageHeader{MessageID: b3},
Header: ccipocr3.RampMessageHeader{MessageID: b3, DestChainSelector: destChainSelector},
},
},
messageGases: []uint64{100, 200, 300},
executionFee: big.NewInt(100),
dataAvailabilityFee: big.NewInt(400),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: new(big.Int).Mul(big.NewInt(100), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
DestDataAvailabilityOverheadGas: 1200,
DestGasPerDataAvailabilityByte: 10,
DestDataAvailabilityMultiplierBps: 200,
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(55200), // 10_000 (exec) + 45_200 (da)
b2: plugintypes.NewUSD18(65200), // 20_000 (exec) + 45_200 (da)
b3: plugintypes.NewUSD18(75200), // 30_000 (exec) + 45_200 (da)
b1: plugintypes.NewUSD18(119700000000000),
b2: plugintypes.NewUSD18(137700000000000),
b3: plugintypes.NewUSD18(155700000000000),
},
wantErr: false,
},
{
name: "message with token amounts affects DA gas calculation",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
TokenAmounts: []ccipocr3.RampTokenAmount{
{
SourcePoolAddress: []byte("source_pool"),
Expand All @@ -381,46 +384,46 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
},
},
messageGases: []uint64{100},
executionFee: big.NewInt(100),
dataAvailabilityFee: big.NewInt(400),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: new(big.Int).Mul(big.NewInt(100), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
DestDataAvailabilityOverheadGas: 1000,
DestGasPerDataAvailabilityByte: 10,
DestDataAvailabilityMultiplierBps: 200,
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(79200), // 10_000 (exec) + 69_200 (da)
b1: plugintypes.NewUSD18(173700000000000),
},
wantErr: false,
},
{
name: "zero DA multiplier results in only overhead gas",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
Data: []byte("some_data"),
},
},
messageGases: []uint64{100},
executionFee: big.NewInt(100),
dataAvailabilityFee: big.NewInt(400),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: new(big.Int).Mul(big.NewInt(100), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
DestDataAvailabilityOverheadGas: 1000,
DestGasPerDataAvailabilityByte: 10,
DestDataAvailabilityMultiplierBps: 0, // Zero multiplier
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(10000), // Only exec cost, DA cost is 0
b1: plugintypes.NewUSD18(18000000000000), // Only exec cost, DA cost is 0
},
wantErr: false,
},
{
name: "large message with multiple tokens",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
TokenAmounts: []ccipocr3.RampTokenAmount{
{
SourcePoolAddress: make([]byte, 100), // Large token data
Expand All @@ -444,16 +447,16 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
},
},
messageGases: []uint64{100},
executionFee: big.NewInt(100),
dataAvailabilityFee: big.NewInt(400),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: new(big.Int).Mul(big.NewInt(100), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
DestDataAvailabilityOverheadGas: 1000,
DestGasPerDataAvailabilityByte: 10,
DestDataAvailabilityMultiplierBps: 200,
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(219600), // 10_000 (exec) + 218_600 (da)
b1: plugintypes.NewUSD18(489600000000000),
},
wantErr: false,
},
Expand Down Expand Up @@ -486,20 +489,20 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
name: "minimal message - only constant parts",
messages: []ccipocr3.Message{
{
Header: ccipocr3.RampMessageHeader{MessageID: b1},
Header: ccipocr3.RampMessageHeader{MessageID: b1, DestChainSelector: destChainSelector},
},
},
messageGases: []uint64{100},
executionFee: big.NewInt(100),
dataAvailabilityFee: big.NewInt(400),
executionFee: new(big.Int).Mul(big.NewInt(20), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
dataAvailabilityFee: new(big.Int).Mul(big.NewInt(100), new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)),
feeComponentsError: nil,
daGasConfig: ccipocr3.DataAvailabilityGasConfig{
DestDataAvailabilityOverheadGas: 1000,
DestGasPerDataAvailabilityByte: 10,
DestDataAvailabilityMultiplierBps: 200,
},
want: map[ccipocr3.Bytes32]plugintypes.USD18{
b1: plugintypes.NewUSD18(53600), // 10_000 (exec) + 43_600 (da)
b1: plugintypes.NewUSD18(116100000000000),
},
wantErr: false,
},
Expand All @@ -516,6 +519,14 @@ func TestCCIPMessageExecCostUSD18Calculator_MessageExecCostUSD18(t *testing.T) {
DataAvailabilityFee: tt.dataAvailabilityFee,
}
mockReader.EXPECT().GetDestChainFeeComponents(ctx).Return(feeComponents, tt.feeComponentsError)
mockReader.EXPECT().GetWrappedNativeTokenPriceUSD(
ctx,
[]ccipocr3.ChainSelector{destChainSelector},
).Return(
map[ccipocr3.ChainSelector]ccipocr3.BigInt{
destChainSelector: nativeTokenPrice,
},
).Maybe()
if !tt.wantErr {
mockReader.EXPECT().GetMedianDataAvailabilityGasConfig(ctx).Return(tt.daGasConfig, nil)
}
Expand Down
4 changes: 3 additions & 1 deletion execute/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
crand "crypto/rand"
"encoding/binary"
"math/big"
"net/http"
"net/http/httptest"
"strings"
Expand Down Expand Up @@ -386,7 +387,8 @@ type msgOption func(*cciptypes.Message)

func withFeeValueJuels(fee int64) msgOption {
return func(m *cciptypes.Message) {
m.FeeValueJuels = cciptypes.NewBigIntFromInt64(fee)
juels := new(big.Int).Mul(big.NewInt(fee), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
m.FeeValueJuels = cciptypes.NewBigInt(juels)
}
}

Expand Down

0 comments on commit ec39846

Please sign in to comment.