From 0b1b4bde6096a803ef6837e2a64fd1a8a20026f2 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Tue, 26 Nov 2024 18:30:05 +0400 Subject: [PATCH 01/10] v2 test --- deployment/ccip/changeset/deploy.go | 6 +- deployment/ccip/changeset/state.go | 18 +- deployment/ccip/changeset/test_assertions.go | 73 +++++ deployment/ccip/changeset/test_helpers.go | 72 ++--- .../smoke/ccip/fee_boosting_test.go | 259 +++++++++++++++--- 5 files changed, 339 insertions(+), 89 deletions(-) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/deploy.go index a53dd7f3f2c..6e4c327f02c 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/deploy.go @@ -7,9 +7,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "golang.org/x/sync/errgroup" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" "github.com/smartcontractkit/chainlink/deployment" @@ -49,7 +50,8 @@ var ( OnRamp deployment.ContractType = "OnRamp" OffRamp deployment.ContractType = "OffRamp" CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" - PriceFeed deployment.ContractType = "PriceFeed" + PriceFeedNativeMock deployment.ContractType = "PriceFeedNativeMock" + PriceFeedLinkMock deployment.ContractType = "PriceFeedLinkMock" // Note test router maps to a regular router contract. TestRouter deployment.ContractType = "TestRouter" Multicall3 deployment.ContractType = "Multicall3" diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 61b58b59af6..c78936edc2c 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -7,7 +7,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_transmitter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/usdc_token_pool" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" chainsel "github.com/smartcontractkit/chain-selectors" @@ -421,7 +420,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.Multicall3 = mc - case deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0).String(): + case deployment.NewTypeAndVersion(PriceFeedLinkMock, deployment.Version1_0_0).String(): feed, err := aggregator_v3_interface.NewAggregatorV3Interface(common.HexToAddress(address), chain.Client) if err != nil { return state, err @@ -429,13 +428,22 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type if state.USDFeeds == nil { state.USDFeeds = make(map[TokenSymbol]*aggregator_v3_interface.AggregatorV3Interface) } - desc, err := feed.Description(&bind.CallOpts{}) + key, ok := MockContractTypeToTokenSymbol[PriceFeedLinkMock] + if !ok { + return state, fmt.Errorf("unknown token %s", PriceFeedLinkMock) + } + state.USDFeeds[key] = feed + case deployment.NewTypeAndVersion(PriceFeedNativeMock, deployment.Version1_0_0).String(): + feed, err := aggregator_v3_interface.NewAggregatorV3Interface(common.HexToAddress(address), chain.Client) if err != nil { return state, err } - key, ok := MockDescriptionToTokenSymbol[desc] + if state.USDFeeds == nil { + state.USDFeeds = make(map[TokenSymbol]*aggregator_v3_interface.AggregatorV3Interface) + } + key, ok := MockContractTypeToTokenSymbol[PriceFeedNativeMock] if !ok { - return state, fmt.Errorf("unknown feed description %s", desc) + return state, fmt.Errorf("unknown token %s", PriceFeedNativeMock) } state.USDFeeds[key] = feed default: diff --git a/deployment/ccip/changeset/test_assertions.go b/deployment/ccip/changeset/test_assertions.go index 770b42e6265..7c197797174 100644 --- a/deployment/ccip/changeset/test_assertions.go +++ b/deployment/ccip/changeset/test_assertions.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -21,6 +22,7 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" ) func ConfirmGasPriceUpdatedForAll( @@ -477,6 +479,77 @@ func ConfirmNoExecConsistentlyWithSeqNr( }, timeout, 3*time.Second, "Expected no execution state change on chain %d (offramp %s) from chain %d with expected sequence number %d", dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) } +// ConfirmPriceUpdate waits for the price feed to update to the expected price. +func ConfirmPriceUpdate( + t *testing.T, + chain deployment.Chain, + state CCIPOnChainState, + symbol TokenSymbol, + expectedPrice *big.Int, +) error { + feed, exists := state.Chains[chain.Selector].USDFeeds[symbol] + if !exists { + return fmt.Errorf("feed not found for token symbol %s on chain %d", symbol, chain.Selector) + } + + aggregator, err := mock_v3_aggregator_contract.NewMockV3AggregatorContract( + feed.Address(), + chain.Client, + ) + if err != nil { + return errors.Wrap(err, "creating aggregator instance") + } + + var duration time.Duration + deadline, ok := t.Deadline() + if ok { + duration = deadline.Sub(time.Now().Add(-1 * time.Minute)) + } else { + duration = 5 * time.Minute + } + timer := time.NewTimer(duration) + defer timer.Stop() + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // if it's simulated backend, commit to ensure mining + if backend, ok := chain.Client.(*memory.Backend); ok { + backend.Commit() + } + + currentPrice, err := aggregator.LatestAnswer(&bind.CallOpts{ + Context: context.Background(), + }) + if err != nil { + t.Logf("Error getting current price: %v", err) + continue + } + + if currentPrice.Cmp(expectedPrice) == 0 { + t.Logf("Price confirmed on chain %d for token %s: price=%s", + chain.Selector, symbol, currentPrice.String()) + return nil + } + + t.Logf("Waiting for price update on chain %d for token %s: current=%s, expected=%s", + chain.Selector, symbol, currentPrice.String(), expectedPrice.String()) + + case <-timer.C: + currentPrice, err := aggregator.LatestAnswer(&bind.CallOpts{ + Context: context.Background(), + }) + if err != nil { + return errors.Wrap(err, "getting final price check") + } + return fmt.Errorf("timed out after waiting %s duration for price update on chain selector %d for token %s: current=%s, expected=%s", + duration.String(), chain.Selector, symbol, currentPrice.String(), expectedPrice.String()) + } + } +} + func GetExecutionState(t *testing.T, source, dest deployment.Chain, offRamp *offramp.OffRamp, expectedSeqNr uint64) (offramp.OffRampSourceChainConfig, uint8) { // if it's simulated backend, commit to ensure mining if backend, ok := source.Client.(*memory.Backend); ok { diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 5e8705d4757..e244594c5b9 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" + "github.com/smartcontractkit/chainlink-ccip/pluginconfig" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" @@ -49,7 +50,6 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/usdc_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_ethusd_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" ) @@ -520,33 +520,17 @@ func ToPackedFee(execFee, daFee *big.Int) *big.Int { return new(big.Int).Or(daShifted, execFee) } -const ( - // MockLinkAggregatorDescription This is the description of the MockV3Aggregator.sol contract - // nolint:lll - // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/tests/MockV3Aggregator.sol#L76-L76 - MockLinkAggregatorDescription = "v0.8/tests/MockV3Aggregator.sol" - // MockWETHAggregatorDescription WETH use description from MockETHUSDAggregator.sol - // nolint:lll - // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/automation/testhelpers/MockETHUSDAggregator.sol#L19-L19 - MockWETHAggregatorDescription = "MockETHUSDAggregator" -) - var ( - MockLinkPrice = deployment.E18Mult(500) - MockWethPrice = big.NewInt(9e8) - // MockDescriptionToTokenSymbol maps a mock feed description to token descriptor - MockDescriptionToTokenSymbol = map[string]TokenSymbol{ - MockLinkAggregatorDescription: LinkSymbol, - MockWETHAggregatorDescription: WethSymbol, - } - MockSymbolToDescription = map[TokenSymbol]string{ - LinkSymbol: MockLinkAggregatorDescription, - WethSymbol: MockWETHAggregatorDescription, - } + MockLinkPrice = deployment.E18Mult(500) + MockWethPrice = deployment.E18Mult(9) MockSymbolToDecimals = map[TokenSymbol]uint8{ LinkSymbol: LinkDecimals, WethSymbol: WethDecimals, } + MockContractTypeToTokenSymbol = map[deployment.ContractType]TokenSymbol{ + PriceFeedLinkMock: LinkSymbol, + PriceFeedNativeMock: WethSymbol, + } ) func DeployFeeds( @@ -555,8 +539,8 @@ func DeployFeeds( chain deployment.Chain, linkPrice *big.Int, wethPrice *big.Int, -) (map[string]common.Address, error) { - linkTV := deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0) +) (map[TokenSymbol]common.Address, error) { + linkTV := deployment.NewTypeAndVersion(PriceFeedLinkMock, deployment.Version1_0_0) mockLinkFeed := func(chain deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface] { linkFeed, tx, _, err1 := mock_v3_aggregator_contract.DeployMockV3Aggregator( chain.DeployerKey, @@ -571,35 +555,37 @@ func DeployFeeds( } } + wethTV := deployment.NewTypeAndVersion(PriceFeedNativeMock, deployment.Version1_0_0) mockWethFeed := func(chain deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface] { - wethFeed, tx, _, err1 := mock_ethusd_aggregator_wrapper.DeployMockETHUSDAggregator( + wethFeed, tx, _, err1 := mock_v3_aggregator_contract.DeployMockV3Aggregator( chain.DeployerKey, chain.Client, - wethPrice, // initialAnswer + WethDecimals, // decimals + wethPrice, // initialAnswer ) aggregatorCr, err2 := aggregator_v3_interface.NewAggregatorV3Interface(wethFeed, chain.Client) return deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface]{ - Address: wethFeed, Contract: aggregatorCr, Tv: linkTV, Tx: tx, Err: multierr.Append(err1, err2), + Address: wethFeed, Contract: aggregatorCr, Tv: wethTV, Tx: tx, Err: multierr.Append(err1, err2), } } - linkFeedAddress, linkFeedDescription, err := deploySingleFeed(lggr, ab, chain, mockLinkFeed, LinkSymbol) + linkFeedAddress, err := deploySingleFeed(lggr, ab, chain, mockLinkFeed, LinkSymbol) if err != nil { return nil, err } - wethFeedAddress, wethFeedDescription, err := deploySingleFeed(lggr, ab, chain, mockWethFeed, WethSymbol) + wethFeedAddress, err := deploySingleFeed(lggr, ab, chain, mockWethFeed, WethSymbol) if err != nil { return nil, err } - descriptionToAddress := map[string]common.Address{ - linkFeedDescription: linkFeedAddress, - wethFeedDescription: wethFeedAddress, + symbolToAddress := map[TokenSymbol]common.Address{ + LinkSymbol: linkFeedAddress, + WethSymbol: wethFeedAddress, } - return descriptionToAddress, nil + return symbolToAddress, nil } func deploySingleFeed( @@ -608,28 +594,16 @@ func deploySingleFeed( chain deployment.Chain, deployFunc func(deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface], symbol TokenSymbol, -) (common.Address, string, error) { - //tokenTV := deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0) +) (common.Address, error) { mockTokenFeed, err := deployment.DeployContract(lggr, chain, ab, deployFunc) if err != nil { lggr.Errorw("Failed to deploy token feed", "err", err, "symbol", symbol) - return common.Address{}, "", err + return common.Address{}, err } lggr.Infow("deployed mockTokenFeed", "addr", mockTokenFeed.Address) - desc, err := mockTokenFeed.Contract.Description(&bind.CallOpts{}) - if err != nil { - lggr.Errorw("Failed to get description", "err", err, "symbol", symbol) - return common.Address{}, "", err - } - - if desc != MockSymbolToDescription[symbol] { - lggr.Errorw("Unexpected description for token", "symbol", symbol, "desc", desc) - return common.Address{}, "", fmt.Errorf("unexpected description: %s", desc) - } - - return mockTokenFeed.Address, desc, nil + return mockTokenFeed.Address, nil } func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, state CCIPOnChainState, sourceCS, destCS, expectedSeqNr uint64) error { diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 4d331c20b7d..9e128e32051 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -1,18 +1,27 @@ package smoke import ( + "context" + "fmt" "math/big" "testing" "time" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" + + "cosmossdk.io/errors" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/test-go/testify/require" "golang.org/x/exp/maps" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/testsetups" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -21,7 +30,6 @@ type feeboostTestCase struct { sender []byte deployedEnv changeset.DeployedEnv onchainState changeset.CCIPOnChainState - initialPrices changeset.InitialPrices priceFeedPrices priceFeedPrices sourceChain, destChain uint64 } @@ -31,64 +39,80 @@ type priceFeedPrices struct { wethPrice *big.Int } -// TODO: find a way to reuse the same test setup for all tests +var ( + initialPrices = changeset.InitialPrices{ + LinkPrice: deployment.E18Mult(5), + WethPrice: deployment.E18Mult(9), + GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), + } +) + func Test_CCIPFeeBoosting(t *testing.T) { setupTestEnv := func(t *testing.T, numChains int) (changeset.DeployedEnv, changeset.CCIPOnChainState, []uint64) { - e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), big.NewInt(9e8), nil) + e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), deployment.E18Mult(9), nil) state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) allChainSelectors := maps.Keys(e.Env.Chains) require.Len(t, allChainSelectors, numChains) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + laneCfg := changeset.LaneConfig{ + SourceSelector: sourceChain, + DestSelector: destChain, + InitialPricesBySource: initialPrices, + FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), + } + + require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) return e, state, allChainSelectors } - t.Run("boost needed due to WETH price increase (also covering gas price inscrease)", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, - priceFeedPrices: priceFeedPrices{ - linkPrice: deployment.E18Mult(5), - wethPrice: big.NewInt(9.9e8), // increase from 9e8 to 9.9e8 - }, - sourceChain: chains[0], - destChain: chains[1], - }) - }) + e, state, chains := setupTestEnv(t, 2) - t.Run("boost needed due to LINK price decrease", func(t *testing.T) { - e, state, chains := setupTestEnv(t, 2) + t.Run("boost needed due to WETH price increase (also covering gas price increase)", func(t *testing.T) { runFeeboostTestCase(feeboostTestCase{ t: t, sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), deployedEnv: e, onchainState: state, - initialPrices: changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - }, priceFeedPrices: priceFeedPrices{ - linkPrice: big.NewInt(4.5e18), // decrease from 5e18 to 4.5e18 - wethPrice: big.NewInt(9e8), + wethPrice: new(big.Int).Mul( + big.NewInt(99), + new(big.Int).Exp(big.NewInt(10), big.NewInt(17), nil), + ), // increase from 9e18 to 9.9e18 }, sourceChain: chains[0], destChain: chains[1], }) }) + + // t.Run("boost needed due to LINK price decrease", func(t *testing.T) { + // runFeeboostTestCase(feeboostTestCase{ + // t: t, + // sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), + // deployedEnv: e, + // onchainState: state, + // priceFeedPrices: priceFeedPrices{ + // linkPrice: big.NewInt(4.5e18), // decrease from 5e18 to 4.5e18 + // }, + // sourceChain: chains[0], + // destChain: chains[1], + // }) + // }) } func runFeeboostTestCase(tc feeboostTestCase) { - require.NoError(tc.t, changeset.AddLaneWithDefaultPricesAndFeeQuoterConfig(tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false)) + // Set initial prices + // setPrices(tc, initialPrices.LinkPrice, initialPrices.WethPrice) startBlocks := make(map[uint64]*uint64) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) @@ -109,6 +133,11 @@ func runFeeboostTestCase(tc feeboostTestCase) { DestChainSelector: tc.destChain, }] = []uint64{msgSentEvent.SequenceNumber} + // Update prices + setPrices(tc, tc.priceFeedPrices.linkPrice, tc.priceFeedPrices.wethPrice) + + require.True(tc.t, willTriggerFeeBoosting(msgSentEvent, tc)) + // hack time.Sleep(30 * time.Second) replayBlocks := make(map[uint64]uint64) @@ -119,3 +148,167 @@ func runFeeboostTestCase(tc feeboostTestCase) { changeset.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) changeset.ConfirmExecWithSeqNrsForAll(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNumExec, startBlocks) } + +func setPrices(tc feeboostTestCase, linkPrice, wethPrice *big.Int) { + feedSelector := tc.deployedEnv.FeedChainSel + + if linkPrice != nil { + require.NoError(tc.t, tc.updatePrice(changeset.LinkSymbol, linkPrice)) + require.NoError(tc.t, changeset.ConfirmPriceUpdate( + tc.t, + tc.deployedEnv.Env.Chains[feedSelector], + tc.onchainState, + changeset.LinkSymbol, + linkPrice, + )) + } + + if wethPrice != nil { + require.NoError(tc.t, tc.updatePrice(changeset.WethSymbol, wethPrice)) + require.NoError(tc.t, changeset.ConfirmPriceUpdate( + tc.t, + tc.deployedEnv.Env.Chains[feedSelector], + tc.onchainState, + changeset.WethSymbol, + wethPrice, + )) + } +} + +func willTriggerFeeBoosting(msgSentEvent *onramp.OnRampCCIPMessageSent, tc feeboostTestCase) bool { + msg := ConvertToMessage(msgSentEvent.Message) + fmt.Println("\n=== Fee Boosting Analysis ===") + fmt.Printf("Message ID: %x\n", msg.Header.MessageID) + + ep := ccipevm.NewGasEstimateProvider() + chainState, exists := tc.onchainState.Chains[tc.sourceChain] + require.True(tc.t, exists) + feeQuoter := chainState.FeeQuoter + + // Get LINK price + linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) + require.NoError(tc.t, err) + fmt.Printf("LINK Price: %s\n", linkPrice.Value.String()) + + // Calculate fee in native token terms + fee := new(big.Int).Div( + new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), + new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), + ) + fmt.Printf("Fee paid (in native token): %s\n", fee.String()) + + // Calculate message gas + messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) + fmt.Printf("Estimated message gas: %s\n", messageGas.String()) + + // Get token and gas prices + nativeTokenAddress := chainState.Weth9.Address() + tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, tc.destChain) + require.NoError(tc.t, err) + fmt.Printf("Raw gas price (uint224): %s\n", tokenAndGasPrice.GasPriceValue.String()) + + // Extract uint112 gas price + gasPrice, err := ConvertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) + require.NoError(tc.t, err) + fmt.Printf("Extracted gas price (uint112): %s\n", gasPrice.String()) + fmt.Printf("Native token price: %s\n", tokenAndGasPrice.TokenPrice.String()) + + // Calculate execution fee + tmp := new(big.Int).Mul(gasPrice, tokenAndGasPrice.TokenPrice) + executionFee := tmp.Div(tmp, big.NewInt(1e18)) + fmt.Printf("Execution fee per gas: %s\n", executionFee.String()) + + // Calculate total execution cost + execCost := new(big.Int).Mul(messageGas, executionFee) + fmt.Printf("Total execution cost: %s\n", execCost.String()) + + // Check if fee boosting will trigger + willBoost := execCost.Cmp(fee) > 0 + fmt.Printf("\nWill fee boosting trigger? %v\n", willBoost) + fmt.Printf("Execution cost / Fee ratio: %.2f\n", + new(big.Float).Quo( + new(big.Float).SetInt(execCost), + new(big.Float).SetInt(fee), + ), + ) + + return execCost.Cmp(fee) > 0 +} + +func ConvertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { + // Create a mask for uint112 (112 bits of 1s) + mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) + + // Extract the lower 112 bits using AND operation + result := new(big.Int).And(gasPrice, mask) + + return result, nil +} + +func ConvertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { + // Convert header + header := cciptypes.RampMessageHeader{ + MessageID: cciptypes.Bytes32(msg.Header.MessageId), + SourceChainSelector: cciptypes.ChainSelector(msg.Header.SourceChainSelector), + DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), + SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), + Nonce: msg.Header.Nonce, + MsgHash: cciptypes.Bytes32{}, // This will be populated by the plugin + OnRamp: cciptypes.UnknownAddress{}, // This will be populated by the CCIP reader + } + + // Convert token amounts + tokenAmounts := make([]cciptypes.RampTokenAmount, len(msg.TokenAmounts)) + for i, ta := range msg.TokenAmounts { + tokenAmounts[i] = cciptypes.RampTokenAmount{ + SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), + DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), + ExtraData: cciptypes.Bytes(ta.ExtraData), + Amount: cciptypes.BigInt{ta.Amount}, + DestExecData: cciptypes.Bytes(ta.DestExecData), + } + } + + return cciptypes.Message{ + Header: header, + Sender: cciptypes.UnknownAddress(msg.Sender.Bytes()), + Data: cciptypes.Bytes(msg.Data), + Receiver: cciptypes.UnknownAddress(msg.Receiver), + ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), + FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), + FeeTokenAmount: cciptypes.BigInt{msg.FeeTokenAmount}, + FeeValueJuels: cciptypes.BigInt{msg.FeeValueJuels}, + TokenAmounts: tokenAmounts, + } +} + +func (tc *feeboostTestCase) updatePrice(symbol changeset.TokenSymbol, price *big.Int) error { + chainSelector := tc.deployedEnv.FeedChainSel + chainState, exists := tc.onchainState.Chains[chainSelector] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", chainSelector) + } + + feed, exists := chainState.USDFeeds[symbol] + if !exists { + return fmt.Errorf("feed not found for token symbol %s on chain %d", symbol, chainSelector) + } + + // Create mock aggregator instance + aggr, err := mock_v3_aggregator_contract.NewMockV3AggregatorContract(feed.Address(), tc.deployedEnv.Env.Chains[chainSelector].Client) + if err != nil { + return errors.Wrapf(err, "creating aggregator instance for %s on chain %d", symbol, chainSelector) + } + + // Update price + auth := tc.deployedEnv.Env.Chains[chainSelector].DeployerKey + tx, err := aggr.UpdateAnswer(auth, price) + if err != nil { + return errors.Wrapf(err, "updating %s price on chain %d", symbol, chainSelector) + } + if _, err := deployment.ConfirmIfNoError(tc.deployedEnv.Env.Chains[tc.sourceChain], tx, err); err != nil { + return err + } + + return nil +} From 1d7fc0d628f7c5c1d3ba5909f096e8fd5c202ea3 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 28 Nov 2024 06:30:04 +0400 Subject: [PATCH 02/10] use last testsetups --- integration-tests/smoke/ccip/fee_boosting_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 9e128e32051..6360585dddc 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -18,7 +18,7 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/integration-tests/testsetups" + testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" From ec154296722aed40d6421ce3957b39729141a597 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 28 Nov 2024 19:32:39 +0400 Subject: [PATCH 03/10] new logic --- .../ccip/changeset/initial_add_chain.go | 1 + deployment/ccip/changeset/test_helpers.go | 5 +- .../smoke/ccip/fee_boosting_test.go | 323 +++++++++--------- .../testsetups/ccip/test_helpers.go | 14 +- 4 files changed, 174 insertions(+), 169 deletions(-) diff --git a/deployment/ccip/changeset/initial_add_chain.go b/deployment/ccip/changeset/initial_add_chain.go index 841f2014204..9e6c0bd75ff 100644 --- a/deployment/ccip/changeset/initial_add_chain.go +++ b/deployment/ccip/changeset/initial_add_chain.go @@ -8,6 +8,7 @@ import ( "time" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/config" diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index f5fcc536d74..44d488a2a4d 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -251,8 +251,9 @@ func mockAttestationResponse() *httptest.Server { } type TestConfigs struct { - IsUSDC bool - IsMultiCall3 bool + IsUSDC bool + IsMultiCall3 bool + IsFeeBoosting bool } func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int, tCfg *TestConfigs) DeployedEnv { diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 6360585dddc..81e9b5cdc14 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -7,225 +7,189 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" - "cosmossdk.io/errors" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/test-go/testify/require" "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" "github.com/smartcontractkit/chainlink/v2/core/logger" ) -type feeboostTestCase struct { - t *testing.T - sender []byte - deployedEnv changeset.DeployedEnv - onchainState changeset.CCIPOnChainState - priceFeedPrices priceFeedPrices - sourceChain, destChain uint64 -} - -type priceFeedPrices struct { - linkPrice *big.Int - wethPrice *big.Int -} - var ( - initialPrices = changeset.InitialPrices{ - LinkPrice: deployment.E18Mult(5), - WethPrice: deployment.E18Mult(9), - GasPrice: changeset.ToPackedFee(big.NewInt(1.8e11), big.NewInt(0)), - } + linkPrice = deployment.E18Mult(100) + wethPrice = deployment.E18Mult(4000) ) func Test_CCIPFeeBoosting(t *testing.T) { - setupTestEnv := func(t *testing.T, numChains int) (changeset.DeployedEnv, changeset.CCIPOnChainState, []uint64) { - e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), deployment.E18Mult(5), deployment.E18Mult(9), nil) - - state, err := changeset.LoadOnchainState(e.Env) - require.NoError(t, err) - - allChainSelectors := maps.Keys(e.Env.Chains) - require.Len(t, allChainSelectors, numChains) - sourceChain := allChainSelectors[0] - destChain := allChainSelectors[1] - t.Log("All chain selectors:", allChainSelectors, - ", home chain selector:", e.HomeChainSel, - ", feed chain selector:", e.FeedChainSel, - ", source chain selector:", sourceChain, - ", dest chain selector:", destChain, + e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), linkPrice, wethPrice, &changeset.TestConfigs{ + IsFeeBoosting: true, + }) + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + require.Len(t, allChainSelectors, 2) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + fetchedGasPriceDest, err := e.Env.Chains[destChain].Client.SuggestGasPrice(tests.Context(t)) + require.NoError(t, err) + originalGasPriceDestUSD := new(big.Int).Div( + new(big.Int).Mul(fetchedGasPriceDest, wethPrice), + big.NewInt(1e18), + ) + t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) + + // Adjust destination gas price to 95% of the current value + adjustedGasPriceDest := + new(big.Int).Div( + new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), + big.NewInt(100), ) + t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) - laneCfg := changeset.LaneConfig{ - SourceSelector: sourceChain, - DestSelector: destChain, - InitialPricesBySource: initialPrices, - FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), - } + initialPrices := changeset.InitialPrices{ + LinkPrice: linkPrice, + WethPrice: wethPrice, + GasPrice: changeset.ToPackedFee(adjustedGasPriceDest, big.NewInt(0)), + } - require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) - return e, state, allChainSelectors + laneCfg := changeset.LaneConfig{ + SourceSelector: sourceChain, + DestSelector: destChain, + InitialPricesBySource: initialPrices, + FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), } + require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) - e, state, chains := setupTestEnv(t, 2) - - t.Run("boost needed due to WETH price increase (also covering gas price increase)", func(t *testing.T) { - runFeeboostTestCase(feeboostTestCase{ - t: t, - sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - deployedEnv: e, - onchainState: state, - priceFeedPrices: priceFeedPrices{ - wethPrice: new(big.Int).Mul( - big.NewInt(99), - new(big.Int).Exp(big.NewInt(10), big.NewInt(17), nil), - ), // increase from 9e18 to 9.9e18 - }, - sourceChain: chains[0], - destChain: chains[1], - }) + fmt.Printf("Changes prices in chain %d for token %s\n", sourceChain, state.Chains[destChain].LinkToken.Address()) + err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ + state.Chains[destChain].LinkToken.Address(): linkPrice, + state.Chains[destChain].Weth9.Address(): wethPrice, }) - - // t.Run("boost needed due to LINK price decrease", func(t *testing.T) { - // runFeeboostTestCase(feeboostTestCase{ - // t: t, - // sender: common.LeftPadBytes(e.Env.Chains[chains[0]].DeployerKey.From.Bytes(), 32), - // deployedEnv: e, - // onchainState: state, - // priceFeedPrices: priceFeedPrices{ - // linkPrice: big.NewInt(4.5e18), // decrease from 5e18 to 4.5e18 - // }, - // sourceChain: chains[0], - // destChain: chains[1], - // }) - // }) -} - -func runFeeboostTestCase(tc feeboostTestCase) { - // Set initial prices - // setPrices(tc, initialPrices.LinkPrice, initialPrices.WethPrice) + require.NoError(t, err) startBlocks := make(map[uint64]*uint64) expectedSeqNum := make(map[changeset.SourceDestPair]uint64) expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - msgSentEvent := changeset.TestSendRequest(tc.t, tc.deployedEnv.Env, tc.onchainState, tc.sourceChain, tc.destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(tc.onchainState.Chains[tc.destChain].Receiver.Address().Bytes(), 32), + + latesthdr, err := e.Env.Chains[sourceChain].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + msgSentEvent := changeset.TestSendRequest(t, e.Env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), Data: []byte("message that needs fee boosting"), TokenAmounts: nil, FeeToken: common.HexToAddress("0x0"), ExtraArgs: nil, }) + startBlocks[sourceChain] = &block expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, + SourceChainSelector: sourceChain, + DestChainSelector: destChain, }] = msgSentEvent.SequenceNumber expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: tc.sourceChain, - DestChainSelector: tc.destChain, + SourceChainSelector: sourceChain, + DestChainSelector: destChain, }] = []uint64{msgSentEvent.SequenceNumber} - // Update prices - setPrices(tc, tc.priceFeedPrices.linkPrice, tc.priceFeedPrices.wethPrice) + err = updateGasPrice(e, state, sourceChain, destChain, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm gas prices are updated + srcFeeQuoter := state.Chains[sourceChain].FeeQuoter + err = changeset.ConfirmGasPriceUpdated(t, e.Env.Chains[destChain], srcFeeQuoter, 0, originalGasPriceDestUSD) + require.NoError(t, err) - require.True(tc.t, willTriggerFeeBoosting(msgSentEvent, tc)) + // Confirm that fee boosting will be triggered + require.True(t, willTriggerFeeBoosting(t, msgSentEvent, state, sourceChain, destChain)) // hack time.Sleep(30 * time.Second) replayBlocks := make(map[uint64]uint64) - replayBlocks[tc.sourceChain] = 1 - replayBlocks[tc.destChain] = 1 - changeset.ReplayLogs(tc.t, tc.deployedEnv.Env.Offchain, replayBlocks) + replayBlocks[sourceChain] = 1 + replayBlocks[destChain] = 1 + changeset.ReplayLogs(t, e.Env.Offchain, replayBlocks) - changeset.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNum, startBlocks) - changeset.ConfirmExecWithSeqNrsForAll(tc.t, tc.deployedEnv.Env, tc.onchainState, expectedSeqNumExec, startBlocks) + // Confirm that the message is committed and executed + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) } -func setPrices(tc feeboostTestCase, linkPrice, wethPrice *big.Int) { - feedSelector := tc.deployedEnv.FeedChainSel - - if linkPrice != nil { - require.NoError(tc.t, tc.updatePrice(changeset.LinkSymbol, linkPrice)) - require.NoError(tc.t, changeset.ConfirmPriceUpdate( - tc.t, - tc.deployedEnv.Env.Chains[feedSelector], - tc.onchainState, - changeset.LinkSymbol, - linkPrice, - )) - } - - if wethPrice != nil { - require.NoError(tc.t, tc.updatePrice(changeset.WethSymbol, wethPrice)) - require.NoError(tc.t, changeset.ConfirmPriceUpdate( - tc.t, - tc.deployedEnv.Env.Chains[feedSelector], - tc.onchainState, - changeset.WethSymbol, - wethPrice, - )) - } -} - -func willTriggerFeeBoosting(msgSentEvent *onramp.OnRampCCIPMessageSent, tc feeboostTestCase) bool { - msg := ConvertToMessage(msgSentEvent.Message) - fmt.Println("\n=== Fee Boosting Analysis ===") - fmt.Printf("Message ID: %x\n", msg.Header.MessageID) +func willTriggerFeeBoosting( + t *testing.T, + msgSentEvent *onramp.OnRampCCIPMessageSent, + state changeset.CCIPOnChainState, + srcChain, destChain uint64) bool { + msg := convertToMessage(msgSentEvent.Message) + t.Log("\n=== Fee Boosting Analysis ===") + t.Logf("Src Chain: %d", msg.Header.SourceChainSelector) + t.Logf("Dest Chain: %d", msg.Header.DestChainSelector) ep := ccipevm.NewGasEstimateProvider() - chainState, exists := tc.onchainState.Chains[tc.sourceChain] - require.True(tc.t, exists) + chainState, exists := state.Chains[srcChain] + require.True(t, exists) feeQuoter := chainState.FeeQuoter + premium, err := feeQuoter.GetPremiumMultiplierWeiPerEth(&bind.CallOpts{Context: context.Background()}, chainState.Weth9.Address()) + require.NoError(t, err) + t.Logf("Premium: %d", premium) + // Get LINK price linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) - require.NoError(tc.t, err) - fmt.Printf("LINK Price: %s\n", linkPrice.Value.String()) + require.NoError(t, err) + t.Logf("LINK Price: %s", linkPrice.Value.String()) + t.Logf("Juels in message: %s", msg.FeeValueJuels.String()) - // Calculate fee in native token terms + // Calculate fee in USD token fee := new(big.Int).Div( new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), ) - fmt.Printf("Fee paid (in native token): %s\n", fee.String()) + t.Logf("Fee paid (in USD token): %s", fee.String()) // Calculate message gas messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) - fmt.Printf("Estimated message gas: %s\n", messageGas.String()) + t.Logf("Estimated message gas: %s", messageGas.String()) // Get token and gas prices nativeTokenAddress := chainState.Weth9.Address() - tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, tc.destChain) - require.NoError(tc.t, err) - fmt.Printf("Raw gas price (uint224): %s\n", tokenAndGasPrice.GasPriceValue.String()) + tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, destChain) + require.NoError(t, err) + t.Logf("Raw gas price (uint224): %s for chain: %d", tokenAndGasPrice.GasPriceValue.String(), destChain) // Extract uint112 gas price - gasPrice, err := ConvertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) - require.NoError(tc.t, err) - fmt.Printf("Extracted gas price (uint112): %s\n", gasPrice.String()) - fmt.Printf("Native token price: %s\n", tokenAndGasPrice.TokenPrice.String()) - - // Calculate execution fee - tmp := new(big.Int).Mul(gasPrice, tokenAndGasPrice.TokenPrice) - executionFee := tmp.Div(tmp, big.NewInt(1e18)) - fmt.Printf("Execution fee per gas: %s\n", executionFee.String()) + gasPrice, err := convertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) + require.NoError(t, err) + t.Logf("Extracted gas price (uint112): %s", gasPrice.String()) + t.Logf("Native token price: %s", tokenAndGasPrice.TokenPrice.String()) // Calculate total execution cost - execCost := new(big.Int).Mul(messageGas, executionFee) - fmt.Printf("Total execution cost: %s\n", execCost.String()) + execCost := new(big.Int).Mul(messageGas, gasPrice) + t.Logf("Total execution cost: %s", execCost.String()) // Check if fee boosting will trigger willBoost := execCost.Cmp(fee) > 0 - fmt.Printf("\nWill fee boosting trigger? %v\n", willBoost) - fmt.Printf("Execution cost / Fee ratio: %.2f\n", + t.Logf("\nWill fee boosting trigger? %v", willBoost) + t.Logf("Execution cost / Fee ratio: %.2f", new(big.Float).Quo( new(big.Float).SetInt(execCost), new(big.Float).SetInt(fee), @@ -235,7 +199,7 @@ func willTriggerFeeBoosting(msgSentEvent *onramp.OnRampCCIPMessageSent, tc feebo return execCost.Cmp(fee) > 0 } -func ConvertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { +func convertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { // Create a mask for uint112 (112 bits of 1s) mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) @@ -245,7 +209,7 @@ func ConvertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { return result, nil } -func ConvertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { +func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { // Convert header header := cciptypes.RampMessageHeader{ MessageID: cciptypes.Bytes32(msg.Header.MessageId), @@ -282,31 +246,58 @@ func ConvertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { } } -func (tc *feeboostTestCase) updatePrice(symbol changeset.TokenSymbol, price *big.Int) error { - chainSelector := tc.deployedEnv.FeedChainSel - chainState, exists := tc.onchainState.Chains[chainSelector] +func updateGasPrice(env changeset.DeployedEnv, state changeset.CCIPOnChainState, srcChain, destChain uint64, gasPrice *big.Int) error { + chainState, exists := state.Chains[srcChain] if !exists { - return fmt.Errorf("chain state not found for selector: %d", chainSelector) + return fmt.Errorf("chain state not found for selector: %d", srcChain) } - feed, exists := chainState.USDFeeds[symbol] - if !exists { - return fmt.Errorf("feed not found for token symbol %s on chain %d", symbol, chainSelector) + feeQuoter := chainState.FeeQuoter + // Update gas price + auth := env.Env.Chains[srcChain].DeployerKey + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: nil, + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + { + DestChainSelector: destChain, + UsdPerUnitGas: gasPrice, + }, + }, + }) + if err != nil { + return errors.Wrapf(err, "updating gas price on chain %d", srcChain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[srcChain], tx, err); err != nil { + return err } - // Create mock aggregator instance - aggr, err := mock_v3_aggregator_contract.NewMockV3AggregatorContract(feed.Address(), tc.deployedEnv.Env.Chains[chainSelector].Client) - if err != nil { - return errors.Wrapf(err, "creating aggregator instance for %s on chain %d", symbol, chainSelector) + return nil +} + +func updateTokensPrices(env changeset.DeployedEnv, state changeset.CCIPOnChainState, chain uint64, tokenPrices map[common.Address]*big.Int) error { + chainState, exists := state.Chains[chain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", chain) } - // Update price - auth := tc.deployedEnv.Env.Chains[chainSelector].DeployerKey - tx, err := aggr.UpdateAnswer(auth, price) + feeQuoter := chainState.FeeQuoter + // Update token prices + auth := env.Env.Chains[chain].DeployerKey + tokenPricesUpdates := make([]fee_quoter.InternalTokenPriceUpdate, 0, len(tokenPrices)) + for token, price := range tokenPrices { + tokenPricesUpdates = append(tokenPricesUpdates, fee_quoter.InternalTokenPriceUpdate{ + SourceToken: token, + UsdPerToken: price, + }) + } + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: tokenPricesUpdates, + GasPriceUpdates: nil, + }) if err != nil { - return errors.Wrapf(err, "updating %s price on chain %d", symbol, chainSelector) + return errors.Wrapf(err, "updating token prices on chain %d", chain) } - if _, err := deployment.ConfirmIfNoError(tc.deployedEnv.Env.Chains[tc.sourceChain], tx, err); err != nil { + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[chain], tx, err); err != nil { return err } diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index 41650f33050..f621c5908ed 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -15,6 +15,7 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" + "github.com/smartcontractkit/chainlink-common/pkg/config" commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" @@ -224,7 +225,18 @@ func NewLocalDevEnvironment( for _, chain := range allChains { timelocksPerChain[chain] = state.Chains[chain].Timelock tokenInfo := tokenConfig.GetTokenInfo(env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) - ocrParams[chain] = changeset.DefaultOCRParams(feedSel, tokenInfo) + + params := changeset.DefaultOCRParams(feedSel, tokenInfo) + + if tCfg.IsFeeBoosting { + // Override specific parameters for fee boosting scenarios + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + params.CommitOffChainConfig.TokenInfo = nil + } + + ocrParams[chain] = params } // Deploy second set of changesets to deploy and configure the CCIP contracts. env, err = commonchangeset.ApplyChangesets(t, env, timelocksPerChain, []commonchangeset.ChangesetApplication{ From 3a70e3bcbad90920a850d60c9238fd79a4d602c9 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 28 Nov 2024 19:35:52 +0400 Subject: [PATCH 04/10] modtidy --- integration-tests/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 371652fbed7..081c7833cb8 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -8,6 +8,7 @@ replace github.com/smartcontractkit/chainlink/v2 => ../ replace github.com/smartcontractkit/chainlink/deployment => ../deployment require ( + cosmossdk.io/errors v1.0.1 dario.cat/mergo v1.0.1 github.com/AlekSi/pointer v1.1.0 github.com/Masterminds/semver/v3 v3.3.0 @@ -70,7 +71,6 @@ require ( cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect - cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/math v1.3.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect From 73d5a829f2830d6d7219e5a357ca8690203e1951 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 28 Nov 2024 20:04:28 +0400 Subject: [PATCH 05/10] lint --- integration-tests/smoke/ccip/fee_boosting_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index 81e9b5cdc14..cae56f7971f 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -217,8 +217,6 @@ func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), Nonce: msg.Header.Nonce, - MsgHash: cciptypes.Bytes32{}, // This will be populated by the plugin - OnRamp: cciptypes.UnknownAddress{}, // This will be populated by the CCIP reader } // Convert token amounts @@ -228,7 +226,7 @@ func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), ExtraData: cciptypes.Bytes(ta.ExtraData), - Amount: cciptypes.BigInt{ta.Amount}, + Amount: cciptypes.BigInt{Int: ta.Amount}, DestExecData: cciptypes.Bytes(ta.DestExecData), } } @@ -240,8 +238,8 @@ func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { Receiver: cciptypes.UnknownAddress(msg.Receiver), ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), - FeeTokenAmount: cciptypes.BigInt{msg.FeeTokenAmount}, - FeeValueJuels: cciptypes.BigInt{msg.FeeValueJuels}, + FeeTokenAmount: cciptypes.BigInt{Int: msg.FeeTokenAmount}, + FeeValueJuels: cciptypes.BigInt{Int: msg.FeeValueJuels}, TokenAmounts: tokenAmounts, } } From 92fa1c3d24021d4d9aab4faf90d416625d752f1d Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 29 Nov 2024 06:23:10 +0400 Subject: [PATCH 06/10] cleaning --- deployment/ccip/changeset/deploy.go | 3 +- deployment/ccip/changeset/state.go | 18 ++--- deployment/ccip/changeset/test_assertions.go | 73 -------------------- deployment/ccip/changeset/test_helpers.go | 71 +++++++++++++------ 4 files changed, 55 insertions(+), 110 deletions(-) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/deploy.go index e501b00a307..3b04ef781b4 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/deploy.go @@ -50,8 +50,7 @@ var ( OnRamp deployment.ContractType = "OnRamp" OffRamp deployment.ContractType = "OffRamp" CapabilitiesRegistry deployment.ContractType = "CapabilitiesRegistry" - PriceFeedNativeMock deployment.ContractType = "PriceFeedNativeMock" - PriceFeedLinkMock deployment.ContractType = "PriceFeedLinkMock" + PriceFeed deployment.ContractType = "PriceFeed" // Note test router maps to a regular router contract. TestRouter deployment.ContractType = "TestRouter" Multicall3 deployment.ContractType = "Multicall3" diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index c78936edc2c..3df6d79c638 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -2,6 +2,7 @@ package changeset import ( "fmt" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_messenger" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_transmitter" @@ -420,7 +421,7 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type return state, err } state.Multicall3 = mc - case deployment.NewTypeAndVersion(PriceFeedLinkMock, deployment.Version1_0_0).String(): + case deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0).String(): feed, err := aggregator_v3_interface.NewAggregatorV3Interface(common.HexToAddress(address), chain.Client) if err != nil { return state, err @@ -428,22 +429,13 @@ func LoadChainState(chain deployment.Chain, addresses map[string]deployment.Type if state.USDFeeds == nil { state.USDFeeds = make(map[TokenSymbol]*aggregator_v3_interface.AggregatorV3Interface) } - key, ok := MockContractTypeToTokenSymbol[PriceFeedLinkMock] - if !ok { - return state, fmt.Errorf("unknown token %s", PriceFeedLinkMock) - } - state.USDFeeds[key] = feed - case deployment.NewTypeAndVersion(PriceFeedNativeMock, deployment.Version1_0_0).String(): - feed, err := aggregator_v3_interface.NewAggregatorV3Interface(common.HexToAddress(address), chain.Client) + desc, err := feed.Description(&bind.CallOpts{}) if err != nil { return state, err } - if state.USDFeeds == nil { - state.USDFeeds = make(map[TokenSymbol]*aggregator_v3_interface.AggregatorV3Interface) - } - key, ok := MockContractTypeToTokenSymbol[PriceFeedNativeMock] + key, ok := MockDescriptionToTokenSymbol[desc] if !ok { - return state, fmt.Errorf("unknown token %s", PriceFeedNativeMock) + return state, fmt.Errorf("unknown feed description %s", desc) } state.USDFeeds[key] = feed default: diff --git a/deployment/ccip/changeset/test_assertions.go b/deployment/ccip/changeset/test_assertions.go index 7c197797174..770b42e6265 100644 --- a/deployment/ccip/changeset/test_assertions.go +++ b/deployment/ccip/changeset/test_assertions.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -22,7 +21,6 @@ import ( "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_v3_aggregator_contract" ) func ConfirmGasPriceUpdatedForAll( @@ -479,77 +477,6 @@ func ConfirmNoExecConsistentlyWithSeqNr( }, timeout, 3*time.Second, "Expected no execution state change on chain %d (offramp %s) from chain %d with expected sequence number %d", dest.Selector, offRamp.Address().String(), source.Selector, expectedSeqNr) } -// ConfirmPriceUpdate waits for the price feed to update to the expected price. -func ConfirmPriceUpdate( - t *testing.T, - chain deployment.Chain, - state CCIPOnChainState, - symbol TokenSymbol, - expectedPrice *big.Int, -) error { - feed, exists := state.Chains[chain.Selector].USDFeeds[symbol] - if !exists { - return fmt.Errorf("feed not found for token symbol %s on chain %d", symbol, chain.Selector) - } - - aggregator, err := mock_v3_aggregator_contract.NewMockV3AggregatorContract( - feed.Address(), - chain.Client, - ) - if err != nil { - return errors.Wrap(err, "creating aggregator instance") - } - - var duration time.Duration - deadline, ok := t.Deadline() - if ok { - duration = deadline.Sub(time.Now().Add(-1 * time.Minute)) - } else { - duration = 5 * time.Minute - } - timer := time.NewTimer(duration) - defer timer.Stop() - ticker := time.NewTicker(2 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // if it's simulated backend, commit to ensure mining - if backend, ok := chain.Client.(*memory.Backend); ok { - backend.Commit() - } - - currentPrice, err := aggregator.LatestAnswer(&bind.CallOpts{ - Context: context.Background(), - }) - if err != nil { - t.Logf("Error getting current price: %v", err) - continue - } - - if currentPrice.Cmp(expectedPrice) == 0 { - t.Logf("Price confirmed on chain %d for token %s: price=%s", - chain.Selector, symbol, currentPrice.String()) - return nil - } - - t.Logf("Waiting for price update on chain %d for token %s: current=%s, expected=%s", - chain.Selector, symbol, currentPrice.String(), expectedPrice.String()) - - case <-timer.C: - currentPrice, err := aggregator.LatestAnswer(&bind.CallOpts{ - Context: context.Background(), - }) - if err != nil { - return errors.Wrap(err, "getting final price check") - } - return fmt.Errorf("timed out after waiting %s duration for price update on chain selector %d for token %s: current=%s, expected=%s", - duration.String(), chain.Selector, symbol, currentPrice.String(), expectedPrice.String()) - } - } -} - func GetExecutionState(t *testing.T, source, dest deployment.Chain, offRamp *offramp.OffRamp, expectedSeqNr uint64) (offramp.OffRampSourceChainConfig, uint8) { // if it's simulated backend, commit to ensure mining if backend, ok := source.Client.(*memory.Backend); ok { diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 44d488a2a4d..6fdb22dd9b0 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -3,6 +3,7 @@ package changeset import ( "context" "fmt" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_ethusd_aggregator_wrapper" "math/big" "net/http" "net/http/httptest" @@ -556,17 +557,33 @@ func ToPackedFee(execFee, daFee *big.Int) *big.Int { return new(big.Int).Or(daShifted, execFee) } +const ( + // MockLinkAggregatorDescription This is the description of the MockV3Aggregator.sol contract + // nolint:lll + // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/tests/MockV3Aggregator.sol#L76-L76 + MockLinkAggregatorDescription = "v0.8/tests/MockV3Aggregator.sol" + // MockWETHAggregatorDescription WETH use description from MockETHUSDAggregator.sol + // nolint:lll + // https://github.com/smartcontractkit/chainlink/blob/a348b98e90527520049c580000a86fb8ceff7fa7/contracts/src/v0.8/automation/testhelpers/MockETHUSDAggregator.sol#L19-L19 + MockWETHAggregatorDescription = "MockETHUSDAggregator" +) + var ( - MockLinkPrice = deployment.E18Mult(500) - MockWethPrice = deployment.E18Mult(9) + MockLinkPrice = deployment.E18Mult(500) + MockWethPrice = big.NewInt(9e8) + // MockDescriptionToTokenSymbol maps a mock feed description to token descriptor + MockDescriptionToTokenSymbol = map[string]TokenSymbol{ + MockLinkAggregatorDescription: LinkSymbol, + MockWETHAggregatorDescription: WethSymbol, + } + MockSymbolToDescription = map[TokenSymbol]string{ + LinkSymbol: MockLinkAggregatorDescription, + WethSymbol: MockWETHAggregatorDescription, + } MockSymbolToDecimals = map[TokenSymbol]uint8{ LinkSymbol: LinkDecimals, WethSymbol: WethDecimals, } - MockContractTypeToTokenSymbol = map[deployment.ContractType]TokenSymbol{ - PriceFeedLinkMock: LinkSymbol, - PriceFeedNativeMock: WethSymbol, - } ) func DeployFeeds( @@ -575,8 +592,8 @@ func DeployFeeds( chain deployment.Chain, linkPrice *big.Int, wethPrice *big.Int, -) (map[TokenSymbol]common.Address, error) { - linkTV := deployment.NewTypeAndVersion(PriceFeedLinkMock, deployment.Version1_0_0) +) (map[string]common.Address, error) { + linkTV := deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0) mockLinkFeed := func(chain deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface] { linkFeed, tx, _, err1 := mock_v3_aggregator_contract.DeployMockV3Aggregator( chain.DeployerKey, @@ -591,37 +608,35 @@ func DeployFeeds( } } - wethTV := deployment.NewTypeAndVersion(PriceFeedNativeMock, deployment.Version1_0_0) mockWethFeed := func(chain deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface] { - wethFeed, tx, _, err1 := mock_v3_aggregator_contract.DeployMockV3Aggregator( + wethFeed, tx, _, err1 := mock_ethusd_aggregator_wrapper.DeployMockETHUSDAggregator( chain.DeployerKey, chain.Client, - WethDecimals, // decimals - wethPrice, // initialAnswer + wethPrice, // initialAnswer ) aggregatorCr, err2 := aggregator_v3_interface.NewAggregatorV3Interface(wethFeed, chain.Client) return deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface]{ - Address: wethFeed, Contract: aggregatorCr, Tv: wethTV, Tx: tx, Err: multierr.Append(err1, err2), + Address: wethFeed, Contract: aggregatorCr, Tv: linkTV, Tx: tx, Err: multierr.Append(err1, err2), } } - linkFeedAddress, err := deploySingleFeed(lggr, ab, chain, mockLinkFeed, LinkSymbol) + linkFeedAddress, linkFeedDescription, err := deploySingleFeed(lggr, ab, chain, mockLinkFeed, LinkSymbol) if err != nil { return nil, err } - wethFeedAddress, err := deploySingleFeed(lggr, ab, chain, mockWethFeed, WethSymbol) + wethFeedAddress, wethFeedDescription, err := deploySingleFeed(lggr, ab, chain, mockWethFeed, WethSymbol) if err != nil { return nil, err } - symbolToAddress := map[TokenSymbol]common.Address{ - LinkSymbol: linkFeedAddress, - WethSymbol: wethFeedAddress, + descriptionToAddress := map[string]common.Address{ + linkFeedDescription: linkFeedAddress, + wethFeedDescription: wethFeedAddress, } - return symbolToAddress, nil + return descriptionToAddress, nil } func deploySingleFeed( @@ -630,16 +645,28 @@ func deploySingleFeed( chain deployment.Chain, deployFunc func(deployment.Chain) deployment.ContractDeploy[*aggregator_v3_interface.AggregatorV3Interface], symbol TokenSymbol, -) (common.Address, error) { +) (common.Address, string, error) { + //tokenTV := deployment.NewTypeAndVersion(PriceFeed, deployment.Version1_0_0) mockTokenFeed, err := deployment.DeployContract(lggr, chain, ab, deployFunc) if err != nil { lggr.Errorw("Failed to deploy token feed", "err", err, "symbol", symbol) - return common.Address{}, err + return common.Address{}, "", err } lggr.Infow("deployed mockTokenFeed", "addr", mockTokenFeed.Address) - return mockTokenFeed.Address, nil + desc, err := mockTokenFeed.Contract.Description(&bind.CallOpts{}) + if err != nil { + lggr.Errorw("Failed to get description", "err", err, "symbol", symbol) + return common.Address{}, "", err + } + + if desc != MockSymbolToDescription[symbol] { + lggr.Errorw("Unexpected description for token", "symbol", symbol, "desc", desc) + return common.Address{}, "", fmt.Errorf("unexpected description: %s", desc) + } + + return mockTokenFeed.Address, desc, nil } func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, state CCIPOnChainState, sourceCS, destCS, expectedSeqNr uint64) error { From 5d88b76f93942d2a9abfc853079a8c4234dfb7ca Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 29 Nov 2024 06:25:56 +0400 Subject: [PATCH 07/10] more clean ups --- deployment/ccip/changeset/deploy.go | 3 +-- deployment/ccip/changeset/initial_add_chain.go | 1 - deployment/ccip/changeset/state.go | 2 +- deployment/ccip/changeset/test_helpers.go | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/deployment/ccip/changeset/deploy.go b/deployment/ccip/changeset/deploy.go index 3b04ef781b4..3aa654862dc 100644 --- a/deployment/ccip/changeset/deploy.go +++ b/deployment/ccip/changeset/deploy.go @@ -7,9 +7,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "golang.org/x/sync/errgroup" - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset/internal" diff --git a/deployment/ccip/changeset/initial_add_chain.go b/deployment/ccip/changeset/initial_add_chain.go index 9e6c0bd75ff..841f2014204 100644 --- a/deployment/ccip/changeset/initial_add_chain.go +++ b/deployment/ccip/changeset/initial_add_chain.go @@ -8,7 +8,6 @@ import ( "time" "github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/timelock" - "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" "github.com/smartcontractkit/chainlink-common/pkg/config" diff --git a/deployment/ccip/changeset/state.go b/deployment/ccip/changeset/state.go index 3df6d79c638..61b58b59af6 100644 --- a/deployment/ccip/changeset/state.go +++ b/deployment/ccip/changeset/state.go @@ -2,12 +2,12 @@ package changeset import ( "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_messenger" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/mock_usdc_token_transmitter" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/usdc_token_pool" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" chainsel "github.com/smartcontractkit/chain-selectors" diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 6fdb22dd9b0..0548971b4db 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -3,7 +3,6 @@ package changeset import ( "context" "fmt" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_ethusd_aggregator_wrapper" "math/big" "net/http" "net/http/httptest" @@ -52,6 +51,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/usdc_token_pool" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/aggregator_v3_interface" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/mock_ethusd_aggregator_wrapper" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/abihelpers" ) From e3b0468d1930f0ee63d8672ab1bdbdb6e00558dc Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 29 Nov 2024 12:09:41 +0400 Subject: [PATCH 08/10] comments --- .github/e2e-tests.yml | 13 ---------- deployment/ccip/changeset/test_helpers.go | 6 ++--- integration-tests/go.mod | 2 +- .../smoke/ccip/fee_boosting_test.go | 25 ++++++++++++++----- .../testsetups/ccip/test_helpers.go | 11 ++------ 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/.github/e2e-tests.yml b/.github/e2e-tests.yml index 9f6495c46f7..4198e24afbc 100644 --- a/.github/e2e-tests.yml +++ b/.github/e2e-tests.yml @@ -974,19 +974,6 @@ runner-test-matrix: E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2,SIMULATED_3 E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/fee_boosting_test.go:* - path: integration-tests/smoke/ccip/fee_boosting_test.go - test_env_type: docker - runs_on: ubuntu-latest - triggers: - - PR E2E Core Tests - - Nightly E2E Tests - test_cmd: cd integration-tests/smoke/ccip && go test fee_boosting_test.go -timeout 15m -test.parallel=1 -count=1 -json - pyroscope_env: ci-smoke-ccipv1_6-evm-simulated - test_env_vars: - E2E_TEST_SELECTED_NETWORK: SIMULATED_1,SIMULATED_2 - E2E_JD_VERSION: 0.6.0 - - id: smoke/ccip/ccip_rmn_test.go:^TestRMN_TwoMessagesOnTwoLanesIncludingBatching$ path: integration-tests/smoke/ccip/ccip_rmn_test.go test_env_type: docker diff --git a/deployment/ccip/changeset/test_helpers.go b/deployment/ccip/changeset/test_helpers.go index 0548971b4db..8c2b1ff9530 100644 --- a/deployment/ccip/changeset/test_helpers.go +++ b/deployment/ccip/changeset/test_helpers.go @@ -252,9 +252,9 @@ func mockAttestationResponse() *httptest.Server { } type TestConfigs struct { - IsUSDC bool - IsMultiCall3 bool - IsFeeBoosting bool + IsUSDC bool + IsMultiCall3 bool + OCRConfigOverride func(CCIPOCRParams) CCIPOCRParams } func NewMemoryEnvironmentWithJobsAndContracts(t *testing.T, lggr logger.Logger, numChains int, numNodes int, tCfg *TestConfigs) DeployedEnv { diff --git a/integration-tests/go.mod b/integration-tests/go.mod index 081c7833cb8..371652fbed7 100644 --- a/integration-tests/go.mod +++ b/integration-tests/go.mod @@ -8,7 +8,6 @@ replace github.com/smartcontractkit/chainlink/v2 => ../ replace github.com/smartcontractkit/chainlink/deployment => ../deployment require ( - cosmossdk.io/errors v1.0.1 dario.cat/mergo v1.0.1 github.com/AlekSi/pointer v1.1.0 github.com/Masterminds/semver/v3 v3.3.0 @@ -71,6 +70,7 @@ require ( cosmossdk.io/api v0.3.1 // indirect cosmossdk.io/core v0.5.1 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect + cosmossdk.io/errors v1.0.1 // indirect cosmossdk.io/math v1.3.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go index cae56f7971f..e6c5e358296 100644 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ b/integration-tests/smoke/ccip/fee_boosting_test.go @@ -7,7 +7,10 @@ import ( "testing" "time" - "cosmossdk.io/errors" + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/test-go/testify/require" @@ -21,7 +24,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink/deployment" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - testsetups "github.com/smartcontractkit/chainlink/integration-tests/testsetups/ccip" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" @@ -33,9 +35,20 @@ var ( ) func Test_CCIPFeeBoosting(t *testing.T) { - e, _, _ := testsetups.NewLocalDevEnvironment(t, logger.TestLogger(t), linkPrice, wethPrice, &changeset.TestConfigs{ - IsFeeBoosting: true, + e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + // Only 1 boost (=OCR round) is enough to cover the fee + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + // Disable token price updates + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable gas price updates + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable token price updates + params.CommitOffChainConfig.TokenInfo = nil + return params + }, }) + state, err := changeset.LoadOnchainState(e.Env) require.NoError(t, err) @@ -58,7 +71,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { ) t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) - // Adjust destination gas price to 95% of the current value + // Adjust destination gas price on source fee quoter to 95% of the current value adjustedGasPriceDest := new(big.Int).Div( new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), @@ -80,7 +93,7 @@ func Test_CCIPFeeBoosting(t *testing.T) { } require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) - fmt.Printf("Changes prices in chain %d for token %s\n", sourceChain, state.Chains[destChain].LinkToken.Address()) + // Update token prices in destination chain FeeQuoter err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ state.Chains[destChain].LinkToken.Address(): linkPrice, state.Chains[destChain].Weth9.Address(): wethPrice, diff --git a/integration-tests/testsetups/ccip/test_helpers.go b/integration-tests/testsetups/ccip/test_helpers.go index f621c5908ed..a2c680ee814 100644 --- a/integration-tests/testsetups/ccip/test_helpers.go +++ b/integration-tests/testsetups/ccip/test_helpers.go @@ -15,8 +15,6 @@ import ( cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" - "github.com/smartcontractkit/chainlink-common/pkg/config" - commonconfig "github.com/smartcontractkit/chainlink-common/pkg/config" "github.com/smartcontractkit/chainlink-testing-framework/lib/blockchain" ctfconfig "github.com/smartcontractkit/chainlink-testing-framework/lib/config" @@ -227,13 +225,8 @@ func NewLocalDevEnvironment( tokenInfo := tokenConfig.GetTokenInfo(env.Logger, state.Chains[chain].LinkToken, state.Chains[chain].Weth9) params := changeset.DefaultOCRParams(feedSel, tokenInfo) - - if tCfg.IsFeeBoosting { - // Override specific parameters for fee boosting scenarios - params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 - params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) - params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) - params.CommitOffChainConfig.TokenInfo = nil + if tCfg.OCRConfigOverride != nil { + params = tCfg.OCRConfigOverride(params) } ocrParams[chain] = params From a741d57dad9c9be7320381588154c1d21d10702a Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 29 Nov 2024 15:00:50 +0400 Subject: [PATCH 09/10] addressing comments 2 --- .github/integration-in-memory-tests.yml | 8 + .../smoke/ccip/fee_boosting_test.go | 316 ------------------ 2 files changed, 8 insertions(+), 316 deletions(-) delete mode 100644 integration-tests/smoke/ccip/fee_boosting_test.go diff --git a/.github/integration-in-memory-tests.yml b/.github/integration-in-memory-tests.yml index f97f3332eb7..9b37438d589 100644 --- a/.github/integration-in-memory-tests.yml +++ b/.github/integration-in-memory-tests.yml @@ -15,4 +15,12 @@ runner-test-matrix: triggers: - PR Integration CCIP Tests test_cmd: cd integration-tests/smoke/ccip && go test ccip_messaging_test.go -timeout 12m -test.parallel=2 -count=1 -json + + - id: smoke/ccip/ccip_fee_boosting_test.go:* + path: integration-tests/smoke/ccip/ccip_fee_boosting_test.go + test_env_type: in-memory + runs_on: ubuntu-latest + triggers: + - PR Integration CCIP Tests + test_cmd: cd integration-tests/smoke/ccip && go test ccip_fee_boosting_test.go -timeout 12m -test.parallel=2 -count=1 -json # END: CCIP tests diff --git a/integration-tests/smoke/ccip/fee_boosting_test.go b/integration-tests/smoke/ccip/fee_boosting_test.go deleted file mode 100644 index e6c5e358296..00000000000 --- a/integration-tests/smoke/ccip/fee_boosting_test.go +++ /dev/null @@ -1,316 +0,0 @@ -package smoke - -import ( - "context" - "fmt" - "math/big" - "testing" - "time" - - "github.com/pkg/errors" - - "github.com/smartcontractkit/chainlink-common/pkg/config" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/test-go/testify/require" - "golang.org/x/exp/maps" - - "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" - "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" - "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" - - cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink/deployment" - "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" - "github.com/smartcontractkit/chainlink/v2/core/logger" -) - -var ( - linkPrice = deployment.E18Mult(100) - wethPrice = deployment.E18Mult(4000) -) - -func Test_CCIPFeeBoosting(t *testing.T) { - e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &changeset.TestConfigs{ - OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { - // Only 1 boost (=OCR round) is enough to cover the fee - params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 - // Disable token price updates - params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) - // Disable gas price updates - params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) - // Disable token price updates - params.CommitOffChainConfig.TokenInfo = nil - return params - }, - }) - - state, err := changeset.LoadOnchainState(e.Env) - require.NoError(t, err) - - allChainSelectors := maps.Keys(e.Env.Chains) - require.Len(t, allChainSelectors, 2) - sourceChain := allChainSelectors[0] - destChain := allChainSelectors[1] - t.Log("All chain selectors:", allChainSelectors, - ", home chain selector:", e.HomeChainSel, - ", feed chain selector:", e.FeedChainSel, - ", source chain selector:", sourceChain, - ", dest chain selector:", destChain, - ) - - fetchedGasPriceDest, err := e.Env.Chains[destChain].Client.SuggestGasPrice(tests.Context(t)) - require.NoError(t, err) - originalGasPriceDestUSD := new(big.Int).Div( - new(big.Int).Mul(fetchedGasPriceDest, wethPrice), - big.NewInt(1e18), - ) - t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) - - // Adjust destination gas price on source fee quoter to 95% of the current value - adjustedGasPriceDest := - new(big.Int).Div( - new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), - big.NewInt(100), - ) - t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) - - initialPrices := changeset.InitialPrices{ - LinkPrice: linkPrice, - WethPrice: wethPrice, - GasPrice: changeset.ToPackedFee(adjustedGasPriceDest, big.NewInt(0)), - } - - laneCfg := changeset.LaneConfig{ - SourceSelector: sourceChain, - DestSelector: destChain, - InitialPricesBySource: initialPrices, - FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), - } - require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) - - // Update token prices in destination chain FeeQuoter - err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ - state.Chains[destChain].LinkToken.Address(): linkPrice, - state.Chains[destChain].Weth9.Address(): wethPrice, - }) - require.NoError(t, err) - - startBlocks := make(map[uint64]*uint64) - expectedSeqNum := make(map[changeset.SourceDestPair]uint64) - expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) - - latesthdr, err := e.Env.Chains[sourceChain].Client.HeaderByNumber(testcontext.Get(t), nil) - require.NoError(t, err) - block := latesthdr.Number.Uint64() - msgSentEvent := changeset.TestSendRequest(t, e.Env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), - Data: []byte("message that needs fee boosting"), - TokenAmounts: nil, - FeeToken: common.HexToAddress("0x0"), - ExtraArgs: nil, - }) - startBlocks[sourceChain] = &block - expectedSeqNum[changeset.SourceDestPair{ - SourceChainSelector: sourceChain, - DestChainSelector: destChain, - }] = msgSentEvent.SequenceNumber - expectedSeqNumExec[changeset.SourceDestPair{ - SourceChainSelector: sourceChain, - DestChainSelector: destChain, - }] = []uint64{msgSentEvent.SequenceNumber} - - err = updateGasPrice(e, state, sourceChain, destChain, originalGasPriceDestUSD) - require.NoError(t, err) - - // Confirm gas prices are updated - srcFeeQuoter := state.Chains[sourceChain].FeeQuoter - err = changeset.ConfirmGasPriceUpdated(t, e.Env.Chains[destChain], srcFeeQuoter, 0, originalGasPriceDestUSD) - require.NoError(t, err) - - // Confirm that fee boosting will be triggered - require.True(t, willTriggerFeeBoosting(t, msgSentEvent, state, sourceChain, destChain)) - - // hack - time.Sleep(30 * time.Second) - replayBlocks := make(map[uint64]uint64) - replayBlocks[sourceChain] = 1 - replayBlocks[destChain] = 1 - changeset.ReplayLogs(t, e.Env.Offchain, replayBlocks) - - // Confirm that the message is committed and executed - changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) - changeset.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) -} - -func willTriggerFeeBoosting( - t *testing.T, - msgSentEvent *onramp.OnRampCCIPMessageSent, - state changeset.CCIPOnChainState, - srcChain, destChain uint64) bool { - msg := convertToMessage(msgSentEvent.Message) - t.Log("\n=== Fee Boosting Analysis ===") - t.Logf("Src Chain: %d", msg.Header.SourceChainSelector) - t.Logf("Dest Chain: %d", msg.Header.DestChainSelector) - - ep := ccipevm.NewGasEstimateProvider() - chainState, exists := state.Chains[srcChain] - require.True(t, exists) - feeQuoter := chainState.FeeQuoter - - premium, err := feeQuoter.GetPremiumMultiplierWeiPerEth(&bind.CallOpts{Context: context.Background()}, chainState.Weth9.Address()) - require.NoError(t, err) - t.Logf("Premium: %d", premium) - - // Get LINK price - linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) - require.NoError(t, err) - t.Logf("LINK Price: %s", linkPrice.Value.String()) - t.Logf("Juels in message: %s", msg.FeeValueJuels.String()) - - // Calculate fee in USD token - fee := new(big.Int).Div( - new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), - new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), - ) - t.Logf("Fee paid (in USD token): %s", fee.String()) - - // Calculate message gas - messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) - t.Logf("Estimated message gas: %s", messageGas.String()) - - // Get token and gas prices - nativeTokenAddress := chainState.Weth9.Address() - tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, destChain) - require.NoError(t, err) - t.Logf("Raw gas price (uint224): %s for chain: %d", tokenAndGasPrice.GasPriceValue.String(), destChain) - - // Extract uint112 gas price - gasPrice, err := convertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) - require.NoError(t, err) - t.Logf("Extracted gas price (uint112): %s", gasPrice.String()) - t.Logf("Native token price: %s", tokenAndGasPrice.TokenPrice.String()) - - // Calculate total execution cost - execCost := new(big.Int).Mul(messageGas, gasPrice) - t.Logf("Total execution cost: %s", execCost.String()) - - // Check if fee boosting will trigger - willBoost := execCost.Cmp(fee) > 0 - t.Logf("\nWill fee boosting trigger? %v", willBoost) - t.Logf("Execution cost / Fee ratio: %.2f", - new(big.Float).Quo( - new(big.Float).SetInt(execCost), - new(big.Float).SetInt(fee), - ), - ) - - return execCost.Cmp(fee) > 0 -} - -func convertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { - // Create a mask for uint112 (112 bits of 1s) - mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) - - // Extract the lower 112 bits using AND operation - result := new(big.Int).And(gasPrice, mask) - - return result, nil -} - -func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { - // Convert header - header := cciptypes.RampMessageHeader{ - MessageID: cciptypes.Bytes32(msg.Header.MessageId), - SourceChainSelector: cciptypes.ChainSelector(msg.Header.SourceChainSelector), - DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), - SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), - Nonce: msg.Header.Nonce, - } - - // Convert token amounts - tokenAmounts := make([]cciptypes.RampTokenAmount, len(msg.TokenAmounts)) - for i, ta := range msg.TokenAmounts { - tokenAmounts[i] = cciptypes.RampTokenAmount{ - SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), - DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), - ExtraData: cciptypes.Bytes(ta.ExtraData), - Amount: cciptypes.BigInt{Int: ta.Amount}, - DestExecData: cciptypes.Bytes(ta.DestExecData), - } - } - - return cciptypes.Message{ - Header: header, - Sender: cciptypes.UnknownAddress(msg.Sender.Bytes()), - Data: cciptypes.Bytes(msg.Data), - Receiver: cciptypes.UnknownAddress(msg.Receiver), - ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), - FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), - FeeTokenAmount: cciptypes.BigInt{Int: msg.FeeTokenAmount}, - FeeValueJuels: cciptypes.BigInt{Int: msg.FeeValueJuels}, - TokenAmounts: tokenAmounts, - } -} - -func updateGasPrice(env changeset.DeployedEnv, state changeset.CCIPOnChainState, srcChain, destChain uint64, gasPrice *big.Int) error { - chainState, exists := state.Chains[srcChain] - if !exists { - return fmt.Errorf("chain state not found for selector: %d", srcChain) - } - - feeQuoter := chainState.FeeQuoter - // Update gas price - auth := env.Env.Chains[srcChain].DeployerKey - tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ - TokenPriceUpdates: nil, - GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ - { - DestChainSelector: destChain, - UsdPerUnitGas: gasPrice, - }, - }, - }) - if err != nil { - return errors.Wrapf(err, "updating gas price on chain %d", srcChain) - } - if _, err := deployment.ConfirmIfNoError(env.Env.Chains[srcChain], tx, err); err != nil { - return err - } - - return nil -} - -func updateTokensPrices(env changeset.DeployedEnv, state changeset.CCIPOnChainState, chain uint64, tokenPrices map[common.Address]*big.Int) error { - chainState, exists := state.Chains[chain] - if !exists { - return fmt.Errorf("chain state not found for selector: %d", chain) - } - - feeQuoter := chainState.FeeQuoter - // Update token prices - auth := env.Env.Chains[chain].DeployerKey - tokenPricesUpdates := make([]fee_quoter.InternalTokenPriceUpdate, 0, len(tokenPrices)) - for token, price := range tokenPrices { - tokenPricesUpdates = append(tokenPricesUpdates, fee_quoter.InternalTokenPriceUpdate{ - SourceToken: token, - UsdPerToken: price, - }) - } - tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ - TokenPriceUpdates: tokenPricesUpdates, - GasPriceUpdates: nil, - }) - if err != nil { - return errors.Wrapf(err, "updating token prices on chain %d", chain) - } - if _, err := deployment.ConfirmIfNoError(env.Env.Chains[chain], tx, err); err != nil { - return err - } - - return nil -} From b3474e15565faa6065c2f8fd6f1f63a2cd8c3dab Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 29 Nov 2024 15:03:44 +0400 Subject: [PATCH 10/10] mv test file --- .../smoke/ccip/ccip_fee_boosting_test.go | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 integration-tests/smoke/ccip/ccip_fee_boosting_test.go diff --git a/integration-tests/smoke/ccip/ccip_fee_boosting_test.go b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go new file mode 100644 index 00000000000..2b14c883238 --- /dev/null +++ b/integration-tests/smoke/ccip/ccip_fee_boosting_test.go @@ -0,0 +1,317 @@ +package smoke + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/pkg/errors" + + "github.com/smartcontractkit/chainlink-common/pkg/config" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/test-go/testify/require" + "golang.org/x/exp/maps" + + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext" + "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/ccipevm" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/fee_quoter" + + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink/deployment" + "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +var ( + linkPrice = deployment.E18Mult(100) + wethPrice = deployment.E18Mult(4000) +) + +func Test_CCIPFeeBoosting(t *testing.T) { + e := changeset.NewMemoryEnvironmentWithJobsAndContracts(t, logger.TestLogger(t), 2, 4, &changeset.TestConfigs{ + OCRConfigOverride: func(params changeset.CCIPOCRParams) changeset.CCIPOCRParams { + // Only 1 boost (=OCR round) is enough to cover the fee + params.ExecuteOffChainConfig.RelativeBoostPerWaitHour = 10 + // Disable token price updates + params.CommitOffChainConfig.TokenPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable gas price updates + params.CommitOffChainConfig.RemoteGasPriceBatchWriteFrequency = *config.MustNewDuration(1_000_000 * time.Hour) + // Disable token price updates + params.CommitOffChainConfig.TokenInfo = nil + return params + }, + }) + + state, err := changeset.LoadOnchainState(e.Env) + require.NoError(t, err) + + allChainSelectors := maps.Keys(e.Env.Chains) + require.Len(t, allChainSelectors, 2) + sourceChain := allChainSelectors[0] + destChain := allChainSelectors[1] + t.Log("All chain selectors:", allChainSelectors, + ", home chain selector:", e.HomeChainSel, + ", feed chain selector:", e.FeedChainSel, + ", source chain selector:", sourceChain, + ", dest chain selector:", destChain, + ) + + fetchedGasPriceDest, err := e.Env.Chains[destChain].Client.SuggestGasPrice(tests.Context(t)) + require.NoError(t, err) + originalGasPriceDestUSD := new(big.Int).Div( + new(big.Int).Mul(fetchedGasPriceDest, wethPrice), + big.NewInt(1e18), + ) + t.Log("Gas price on dest chain (USD):", originalGasPriceDestUSD) + + // Adjust destination gas price on source fee quoter to 95% of the current value + adjustedGasPriceDest := + new(big.Int).Div( + new(big.Int).Mul(originalGasPriceDestUSD, big.NewInt(95)), + big.NewInt(100), + ) + t.Log("Adjusted gas price on dest chain:", adjustedGasPriceDest) + + initialPrices := changeset.InitialPrices{ + LinkPrice: linkPrice, + WethPrice: wethPrice, + GasPrice: changeset.ToPackedFee(adjustedGasPriceDest, big.NewInt(0)), + } + + laneCfg := changeset.LaneConfig{ + SourceSelector: sourceChain, + DestSelector: destChain, + InitialPricesBySource: initialPrices, + FeeQuoterDestChain: changeset.DefaultFeeQuoterDestChainConfig(), + } + require.NoError(t, changeset.AddLane(e.Env, state, laneCfg, false)) + + // Update token prices in destination chain FeeQuoter + err = updateTokensPrices(e, state, destChain, map[common.Address]*big.Int{ + state.Chains[destChain].LinkToken.Address(): linkPrice, + state.Chains[destChain].Weth9.Address(): wethPrice, + }) + require.NoError(t, err) + + startBlocks := make(map[uint64]*uint64) + expectedSeqNum := make(map[changeset.SourceDestPair]uint64) + expectedSeqNumExec := make(map[changeset.SourceDestPair][]uint64) + + latesthdr, err := e.Env.Chains[sourceChain].Client.HeaderByNumber(testcontext.Get(t), nil) + require.NoError(t, err) + block := latesthdr.Number.Uint64() + msgSentEvent := changeset.TestSendRequest(t, e.Env, state, sourceChain, destChain, false, router.ClientEVM2AnyMessage{ + Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + Data: []byte("message that needs fee boosting"), + TokenAmounts: nil, + FeeToken: common.HexToAddress("0x0"), + ExtraArgs: nil, + }) + startBlocks[sourceChain] = &block + expectedSeqNum[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = msgSentEvent.SequenceNumber + expectedSeqNumExec[changeset.SourceDestPair{ + SourceChainSelector: sourceChain, + DestChainSelector: destChain, + }] = []uint64{msgSentEvent.SequenceNumber} + + err = updateGasPrice(e, state, sourceChain, destChain, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm gas prices are updated + srcFeeQuoter := state.Chains[sourceChain].FeeQuoter + err = changeset.ConfirmGasPriceUpdated(t, e.Env.Chains[destChain], srcFeeQuoter, 0, originalGasPriceDestUSD) + require.NoError(t, err) + + // Confirm that fee boosting will be triggered + require.True(t, willTriggerFeeBoosting(t, msgSentEvent, state, sourceChain, destChain)) + + // hack + time.Sleep(30 * time.Second) + replayBlocks := make(map[uint64]uint64) + replayBlocks[sourceChain] = 1 + replayBlocks[destChain] = 1 + changeset.ReplayLogs(t, e.Env.Offchain, replayBlocks) + + // Confirm that the message is committed and executed + changeset.ConfirmCommitForAllWithExpectedSeqNums(t, e.Env, state, expectedSeqNum, startBlocks) + changeset.ConfirmExecWithSeqNrsForAll(t, e.Env, state, expectedSeqNumExec, startBlocks) +} + +// TODO: Find a more accurate way to determine if fee boosting will be triggered +func willTriggerFeeBoosting( + t *testing.T, + msgSentEvent *onramp.OnRampCCIPMessageSent, + state changeset.CCIPOnChainState, + srcChain, destChain uint64) bool { + msg := convertToMessage(msgSentEvent.Message) + t.Log("\n=== Fee Boosting Analysis ===") + t.Logf("Src Chain: %d", msg.Header.SourceChainSelector) + t.Logf("Dest Chain: %d", msg.Header.DestChainSelector) + + ep := ccipevm.NewGasEstimateProvider() + chainState, exists := state.Chains[srcChain] + require.True(t, exists) + feeQuoter := chainState.FeeQuoter + + premium, err := feeQuoter.GetPremiumMultiplierWeiPerEth(&bind.CallOpts{Context: context.Background()}, chainState.Weth9.Address()) + require.NoError(t, err) + t.Logf("Premium: %d", premium) + + // Get LINK price + linkPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{Context: context.Background()}, chainState.LinkToken.Address()) + require.NoError(t, err) + t.Logf("LINK Price: %s", linkPrice.Value.String()) + t.Logf("Juels in message: %s", msg.FeeValueJuels.String()) + + // Calculate fee in USD token + fee := new(big.Int).Div( + new(big.Int).Mul(linkPrice.Value, msg.FeeValueJuels.Int), + new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil), + ) + t.Logf("Fee paid (in USD token): %s", fee.String()) + + // Calculate message gas + messageGas := new(big.Int).SetUint64(ep.CalculateMessageMaxGas(msg)) + t.Logf("Estimated message gas: %s", messageGas.String()) + + // Get token and gas prices + nativeTokenAddress := chainState.Weth9.Address() + tokenAndGasPrice, err := feeQuoter.GetTokenAndGasPrices(&bind.CallOpts{Context: context.Background()}, nativeTokenAddress, destChain) + require.NoError(t, err) + t.Logf("Raw gas price (uint224): %s for chain: %d", tokenAndGasPrice.GasPriceValue.String(), destChain) + + // Extract uint112 gas price + gasPrice, err := convertGasPriceToUint112(tokenAndGasPrice.GasPriceValue) + require.NoError(t, err) + t.Logf("Extracted gas price (uint112): %s", gasPrice.String()) + t.Logf("Native token price: %s", tokenAndGasPrice.TokenPrice.String()) + + // Calculate total execution cost + execCost := new(big.Int).Mul(messageGas, gasPrice) + t.Logf("Total execution cost: %s", execCost.String()) + + // Check if fee boosting will trigger + willBoost := execCost.Cmp(fee) > 0 + t.Logf("\nWill fee boosting trigger? %v", willBoost) + t.Logf("Execution cost / Fee ratio: %.2f", + new(big.Float).Quo( + new(big.Float).SetInt(execCost), + new(big.Float).SetInt(fee), + ), + ) + + return execCost.Cmp(fee) > 0 +} + +func convertGasPriceToUint112(gasPrice *big.Int) (*big.Int, error) { + // Create a mask for uint112 (112 bits of 1s) + mask := new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 112), big.NewInt(1)) + + // Extract the lower 112 bits using AND operation + result := new(big.Int).And(gasPrice, mask) + + return result, nil +} + +func convertToMessage(msg onramp.InternalEVM2AnyRampMessage) cciptypes.Message { + // Convert header + header := cciptypes.RampMessageHeader{ + MessageID: cciptypes.Bytes32(msg.Header.MessageId), + SourceChainSelector: cciptypes.ChainSelector(msg.Header.SourceChainSelector), + DestChainSelector: cciptypes.ChainSelector(msg.Header.DestChainSelector), + SequenceNumber: cciptypes.SeqNum(msg.Header.SequenceNumber), + Nonce: msg.Header.Nonce, + } + + // Convert token amounts + tokenAmounts := make([]cciptypes.RampTokenAmount, len(msg.TokenAmounts)) + for i, ta := range msg.TokenAmounts { + tokenAmounts[i] = cciptypes.RampTokenAmount{ + SourcePoolAddress: cciptypes.UnknownAddress(ta.SourcePoolAddress.Bytes()), + DestTokenAddress: cciptypes.UnknownAddress(ta.DestTokenAddress), + ExtraData: cciptypes.Bytes(ta.ExtraData), + Amount: cciptypes.BigInt{Int: ta.Amount}, + DestExecData: cciptypes.Bytes(ta.DestExecData), + } + } + + return cciptypes.Message{ + Header: header, + Sender: cciptypes.UnknownAddress(msg.Sender.Bytes()), + Data: cciptypes.Bytes(msg.Data), + Receiver: cciptypes.UnknownAddress(msg.Receiver), + ExtraArgs: cciptypes.Bytes(msg.ExtraArgs), + FeeToken: cciptypes.UnknownAddress(msg.FeeToken.Bytes()), + FeeTokenAmount: cciptypes.BigInt{Int: msg.FeeTokenAmount}, + FeeValueJuels: cciptypes.BigInt{Int: msg.FeeValueJuels}, + TokenAmounts: tokenAmounts, + } +} + +func updateGasPrice(env changeset.DeployedEnv, state changeset.CCIPOnChainState, srcChain, destChain uint64, gasPrice *big.Int) error { + chainState, exists := state.Chains[srcChain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", srcChain) + } + + feeQuoter := chainState.FeeQuoter + // Update gas price + auth := env.Env.Chains[srcChain].DeployerKey + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: nil, + GasPriceUpdates: []fee_quoter.InternalGasPriceUpdate{ + { + DestChainSelector: destChain, + UsdPerUnitGas: gasPrice, + }, + }, + }) + if err != nil { + return errors.Wrapf(err, "updating gas price on chain %d", srcChain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[srcChain], tx, err); err != nil { + return err + } + + return nil +} + +func updateTokensPrices(env changeset.DeployedEnv, state changeset.CCIPOnChainState, chain uint64, tokenPrices map[common.Address]*big.Int) error { + chainState, exists := state.Chains[chain] + if !exists { + return fmt.Errorf("chain state not found for selector: %d", chain) + } + + feeQuoter := chainState.FeeQuoter + // Update token prices + auth := env.Env.Chains[chain].DeployerKey + tokenPricesUpdates := make([]fee_quoter.InternalTokenPriceUpdate, 0, len(tokenPrices)) + for token, price := range tokenPrices { + tokenPricesUpdates = append(tokenPricesUpdates, fee_quoter.InternalTokenPriceUpdate{ + SourceToken: token, + UsdPerToken: price, + }) + } + tx, err := feeQuoter.UpdatePrices(auth, fee_quoter.InternalPriceUpdates{ + TokenPriceUpdates: tokenPricesUpdates, + GasPriceUpdates: nil, + }) + if err != nil { + return errors.Wrapf(err, "updating token prices on chain %d", chain) + } + if _, err := deployment.ConfirmIfNoError(env.Env.Chains[chain], tx, err); err != nil { + return err + } + + return nil +}