Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert the revert on fee boosting + using mathlibs #309

Merged
merged 10 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading