diff --git a/client_configuration.go b/client_configuration.go index 4cdf75df9b..4e13a48a9f 100644 --- a/client_configuration.go +++ b/client_configuration.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "math/big" + "os" "strconv" "sync" "testing" @@ -26,6 +27,7 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/burn_mint_erc677" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/generated/erc20" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/testhelpers" "github.com/stretchr/testify/require" @@ -359,18 +361,76 @@ func (client *CCIPClient) setAllowList(t *testing.T) { } //nolint:all -func (client *CCIPClient) setRateLimiterConfig(t *testing.T) { +func (client *CCIPClient) setTokenPoolRateLimiterConfig(t *testing.T, rToken rhea.Token, capacity, rate *big.Int) { + tokenDetails, ok := client.Source.SupportedTokens[rToken] + if !ok { + t.Fatalf("Token %s not found in supported tokens", rToken) + } + if tokenDetails.TokenPoolType == rhea.FeeTokenOnly { + t.Fatalf("Token %s is fee token only", rToken) + } + tx, err := tokenDetails.Pool.SetOnRampRateLimiterConfig( + client.Source.Owner, client.Source.OnRamp.Address(), + lock_release_token_pool.RateLimiterConfig{ + Capacity: capacity, + Rate: rate, + IsEnabled: true, + }) + shared.RequireNoError(t, err) + err = shared.WaitForMined(client.Source.logger, client.Source.Client.Client, tx.Hash(), true) + shared.RequireNoError(t, err) +} + +func (client *CCIPClient) SendTxWithPercentageAggregatedCapacity(t *testing.T, rToken rhea.Token, percentage int64) { + // send tx with percentage aggregated capacity + // 1. get aggregated capacity + // 2. send tx with percentage aggregated capacity + // 3. check that tx is sent + tokenDetails, ok := client.Source.SupportedTokens[rToken] + require.True(t, ok, "Token %s not found in supported tokens", rToken) + rlOnRamp, err := client.Source.OnRamp.CurrentRateLimiterState(nil) + require.NoError(t, err) + require.True(t, rlOnRamp.IsEnabled, "OnRamp rate limiter is not enabled") + tokenPrice, err := client.Source.PriceRegistry.GetTokenPrice(nil, tokenDetails.Token) + require.NoError(t, err) + tokensForOnRampCapacity := new(big.Int).Mul(rToken.Multiplier(), + new(big.Int).Div(rlOnRamp.Capacity, tokenPrice.Value)) + tokenAmountToSend, err := rhea.PercentageOfValue(tokensForOnRampCapacity, percentage) + shared.RequireNoError(t, err) + t.Logf("SendTxWithPercentageAggregatedCapacity: chainId=%d, rToken=%s, percentage=%d, tokenAmountToSend=%s", + client.Source.ChainId, rToken, percentage, tokenAmountToSend.String()) + + client.SendTx(t, client.Source.Owner, common.Address{}, []rhea.Token{rToken}, tokenAmountToSend) +} + +func (client *CCIPClient) SendTxWithPercentageTokenPoolCapacity(t *testing.T, rToken rhea.Token, percentage int64) { + tokenDetails, ok := client.Source.SupportedTokens[rToken] + require.True(t, ok, "Token %s not found in supported tokens", rToken) + rlOnPool, err := tokenDetails.Pool.CurrentOnRampRateLimiterState(nil, client.Source.OnRamp.Address()) + require.NoError(t, err) + require.True(t, rlOnPool.IsEnabled, "Token Pool rate limiter is not enabled") + tokenAmountToSend, err := rhea.PercentageOfValue(rlOnPool.Capacity, percentage) + shared.RequireNoError(t, err) + t.Logf("SendTxWithPercentageTokenPoolCapacity: chainId=%d, rToken=%s, percentage=%d, tokenAmountToSend=%s", + client.Source.ChainId, rToken, percentage, tokenAmountToSend.String()) + client.SendTx(t, client.Source.Owner, common.Address{}, []rhea.Token{rToken}, tokenAmountToSend) +} + +//nolint:all +func (client *CCIPClient) setRampsRateLimiterConfig(t *testing.T, capacity, rate *big.Int) { tx, err := client.Source.OnRamp.SetRateLimiterConfig(client.Source.Owner, evm_2_evm_onramp.RateLimiterConfig{ - Capacity: rhea.UsdToRateLimitValue(rhea.RATE_LIMIT_CAPACITY_DOLLAR), - Rate: rhea.UsdToRateLimitValue(rhea.RATE_LIMIT_RATE_DOLLAR), + Capacity: capacity, + Rate: rate, + IsEnabled: true, }) shared.RequireNoError(t, err) err = shared.WaitForMined(client.Source.logger, client.Source.Client.Client, tx.Hash(), true) shared.RequireNoError(t, err) tx, err = client.Dest.OffRamp.SetRateLimiterConfig(client.Dest.Owner, evm_2_evm_offramp.RateLimiterConfig{ - Capacity: rhea.UsdToRateLimitValue(rhea.RATE_LIMIT_CAPACITY_DOLLAR), - Rate: rhea.UsdToRateLimitValue(rhea.RATE_LIMIT_RATE_DOLLAR), + Capacity: capacity, + Rate: rate, + IsEnabled: true, }) shared.RequireNoError(t, err) err = shared.WaitForMined(client.Dest.logger, client.Dest.Client.Client, tx.Hash(), true) @@ -526,6 +586,18 @@ func (client *Client) ApproveLinkFrom(t *testing.T, user *bind.TransactOpts, app client.logger.Warnf("Link approved %s", helpers.ExplorerLink(int64(client.ChainId), tx.Hash())) } +func (client *Client) ApproveTokenFrom(t *testing.T, user *bind.TransactOpts, token, approvedFor common.Address, amount *big.Int) { + client.logger.Warnf("Approving %d link for %s", amount.Int64(), approvedFor.Hex()) + erc20Token, err := erc20.NewERC20(token, client.Client) + require.NoError(t, err) + tx, err := erc20Token.Approve(user, approvedFor, amount) + require.NoError(t, err) + + err = shared.WaitForMined(client.logger, client.Client, tx.Hash(), true) + require.NoError(t, err) + client.logger.Warnf("Token %s approved %s", token.Hex(), helpers.ExplorerLink(int64(client.ChainId), tx.Hash())) +} + func (client *Client) ApproveLink(t *testing.T, approvedFor common.Address, amount *big.Int) { client.ApproveLinkFrom(t, client.Owner, approvedFor, amount) } @@ -951,87 +1023,27 @@ func (client *CCIPClient) SyncTokenPools() { } func (client *CCIPClient) ccipSendBasicTx(t *testing.T) { - msg := client.getBasicTx(t, client.Source.LinkTokenAddress, false) - - ///////////////////////////////// - // ADD TOKENS AND/OR DATA HERE // - ///////////////////////////////// - - DATA := []byte("") - TOKENS := []rhea.Token{} - AMOUNTS := []*big.Int{} - - ///////////////////////////////// - // END TOKENS AND/OR DATA HERE // - ///////////////////////////////// - - if len(TOKENS) != len(AMOUNTS) { - t.Error("Tokens and amounts need to be the same length") - t.FailNow() - } - - addToFeeApprove := big.NewInt(0) - - for i, token := range TOKENS { - msg.TokenAmounts = append(msg.TokenAmounts, router.ClientEVMTokenAmount{ - Token: client.Source.SupportedTokens[token].Token, - Amount: AMOUNTS[i], - }) - - if token == rhea.LINK { - addToFeeApprove = AMOUNTS[i] - continue - } - - client.Source.logger.Infof("Approving %d %s", AMOUNTS[i], token) - - ERC20, err := burn_mint_erc677.NewBurnMintERC677(client.Source.SupportedTokens[token].Token, client.Source.Client.Client) - require.NoError(t, err) - - tx, err := ERC20.Approve(client.Source.Owner, client.Source.Router.Address(), AMOUNTS[i]) - require.NoError(t, err) - err = shared.WaitForMined(client.Source.logger, client.Source.Client.Client, tx.Hash(), true) - shared.RequireNoError(t, err) - } - - msg.Data = DATA - - fee, err := client.Source.Router.GetFee(&bind.CallOpts{}, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - - // If link was sent, add it to the fee for the approval - fee = new(big.Int).Add(fee, addToFeeApprove) - - client.Source.ApproveLinkFrom(t, client.Source.Owner, client.Source.Router.Address(), fee) - - sourceBlockNumber := GetCurrentBlockNumber(client.Source.Client.Client) - DestBlockNum := GetCurrentBlockNumber(client.Dest.Client.Client) - - tx, err := client.Source.Router.CcipSend(client.Source.Owner, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - client.Source.logger.Warnf("Message sent for max %d gas %s", tx.Gas(), helpers.ExplorerLink(int64(client.Source.ChainId), tx.Hash())) - - sendRequested := WaitForCrossChainSendRequest(client.Source, sourceBlockNumber, tx.Hash()) - require.NoError(t, client.WaitForCommit(DestBlockNum), "waiting for commit") - require.NoError(t, client.WaitForExecution(DestBlockNum, sendRequested.Message.SequenceNumber), "waiting for execution") + destBlockNum, sendRequested := client.sendLinkTx(t, client.Source.Owner, nil) + require.NoError(t, client.WaitForCommit(destBlockNum), "waiting for commit") + require.NoError(t, client.WaitForExecution(destBlockNum, sendRequested.Message.SequenceNumber), "waiting for execution") } func (client *CCIPClient) TestGasVariousTxs(t *testing.T) { - client.sendLinkTx(t, client.Source.Owner, false) + client.sendLinkTx(t, client.Source.Owner, nil) // we wait in between txs to make sure they're not batched time.Sleep(5 * time.Second) - client.sendWrappedNativeTx(t, client.Source.Owner, false) + client.sendWrappedNativeTx(t, client.Source.Owner, nil) time.Sleep(5 * time.Second) - client.sendNativeTx(t, client.Source.Owner, false) + client.sendNativeTx(t, client.Source.Owner, nil) time.Sleep(5 * time.Second) - client.sendLinkTx(t, client.Source.Owner, true) + client.sendLinkTx(t, client.Source.Owner, []rhea.Token{rhea.LINK}) time.Sleep(5 * time.Second) - client.sendWrappedNativeTx(t, client.Source.Owner, true) + client.sendWrappedNativeTx(t, client.Source.Owner, []rhea.Token{rhea.LINK}) time.Sleep(5 * time.Second) - client.sendNativeTx(t, client.Source.Owner, true) + client.sendNativeTx(t, client.Source.Owner, []rhea.Token{rhea.LINK}) } -func (client *CCIPClient) getBasicTx(t *testing.T, feeToken common.Address, includesToken bool) router.ClientEVM2AnyMessage { +func (client *CCIPClient) getBasicTx(t *testing.T, feeToken common.Address, tokens []common.Address, amount *big.Int) router.ClientEVM2AnyMessage { msg := router.ClientEVM2AnyMessage{ Receiver: testhelpers.MustEncodeAddress(t, client.Dest.Owner.From), Data: []byte{}, @@ -1039,72 +1051,80 @@ func (client *CCIPClient) getBasicTx(t *testing.T, feeToken common.Address, incl FeeToken: feeToken, ExtraArgs: []byte{}, } - if includesToken { + + for _, tokenAddr := range tokens { msg.TokenAmounts = append(msg.TokenAmounts, router.ClientEVMTokenAmount{ - Token: client.Source.LinkTokenAddress, - Amount: big.NewInt(100), + Token: tokenAddr, + Amount: amount, }) } return msg } -func (client *CCIPClient) sendLinkTx(t *testing.T, from *bind.TransactOpts, token bool) *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested { - msg := client.getBasicTx(t, client.Source.LinkTokenAddress, token) - +func (client *CCIPClient) ApproveTokens(t *testing.T, from *bind.TransactOpts, msg router.ClientEVM2AnyMessage) *big.Int { + isNative := msg.FeeToken == (common.Address{}) fee, err := client.Source.Router.GetFee(&bind.CallOpts{}, client.Dest.ChainSelector, msg) shared.RequireNoError(t, err) - if token { - fee.Add(fee, msg.TokenAmounts[0].Amount) + feeApproved := false + + for _, tokenAndAmount := range msg.TokenAmounts { + token := tokenAndAmount.Token + amount := tokenAndAmount.Amount + if !isNative && token == msg.FeeToken { + amount = new(big.Int).Add(fee, amount) + feeApproved = true + } + client.Source.ApproveTokenFrom(t, from, token, client.Source.Router.Address(), amount) + } + if !feeApproved && !isNative { + client.Source.ApproveTokenFrom(t, from, msg.FeeToken, client.Source.Router.Address(), fee) } + return fee +} - client.Source.ApproveLinkFrom(t, from, client.Source.Router.Address(), fee) +func (client *CCIPClient) SendTx(t *testing.T, from *bind.TransactOpts, feeToken common.Address, tokens []rhea.Token, amount *big.Int) (uint64, *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested) { + var bridgeTokens []common.Address + for _, rToken := range tokens { + token, ok := client.Source.SupportedTokens[rToken] + if !ok { + require.FailNow(t, "token not supported") + } + bridgeTokens = append(bridgeTokens, token.Token) + } + // if sending to a different receiver address, set it here + receiverDapp := os.Getenv("CCIP_RECEIVER") + msg := client.getBasicTx(t, feeToken, bridgeTokens, amount) + if receiverDapp != "" && common.IsHexAddress(receiverDapp) { + msg.Receiver = testhelpers.MustEncodeAddress(t, common.HexToAddress(receiverDapp)) + } + fee := client.ApproveTokens(t, from, msg) + // for native fee token, we need to send the fee in the tx value + if msg.FeeToken == (common.Address{}) { + from.Value = fee + } sourceBlockNumber := GetCurrentBlockNumber(client.Source.Client.Client) + destBlockNumber := GetCurrentBlockNumber(client.Dest.Client.Client) tx, err := client.Source.Router.CcipSend(from, client.Dest.ChainSelector, msg) shared.RequireNoError(t, err) client.Source.logger.Warnf("Message sent for max %d gas %s", tx.Gas(), helpers.ExplorerLink(int64(client.Source.ChainId), tx.Hash())) - return WaitForCrossChainSendRequest(client.Source, sourceBlockNumber, tx.Hash()) + return destBlockNumber, WaitForCrossChainSendRequest(client.Source, sourceBlockNumber, tx.Hash()) } -func (client *CCIPClient) sendWrappedNativeTx(t *testing.T, from *bind.TransactOpts, token bool) *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested { - msg := client.getBasicTx(t, client.Source.WrappedNative.Address(), token) - - fee, err := client.Source.Router.GetFee(&bind.CallOpts{}, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - - tx, err := client.Source.WrappedNative.Approve(from, client.Source.Router.Address(), fee) - shared.RequireNoError(t, err) - err = shared.WaitForMined(client.Source.logger, client.Source.Client.Client, tx.Hash(), true) - shared.RequireNoError(t, err) - if token { - client.Source.ApproveLinkFrom(t, from, client.Source.Router.Address(), msg.TokenAmounts[0].Amount) - } - - sourceBlockNumber := GetCurrentBlockNumber(client.Source.Client.Client) - tx, err = client.Source.Router.CcipSend(from, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - client.Source.logger.Warnf("Message sent for max %d gas %s", tx.Gas(), helpers.ExplorerLink(int64(client.Source.ChainId), tx.Hash())) - return WaitForCrossChainSendRequest(client.Source, sourceBlockNumber, tx.Hash()) +func (client *CCIPClient) sendLinkTx(t *testing.T, from *bind.TransactOpts, tokens []rhea.Token) (uint64, *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested) { + feeToken := client.Source.LinkTokenAddress + return client.SendTx(t, from, feeToken, tokens, big.NewInt(100)) } -func (client *CCIPClient) sendNativeTx(t *testing.T, from *bind.TransactOpts, token bool) *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested { - msg := client.getBasicTx(t, common.Address{}, token) - - fee, err := client.Source.Router.GetFee(&bind.CallOpts{}, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - - if token { - client.Source.ApproveLinkFrom(t, from, client.Source.Router.Address(), msg.TokenAmounts[0].Amount) - } - from.Value = fee +func (client *CCIPClient) sendWrappedNativeTx(t *testing.T, from *bind.TransactOpts, tokens []rhea.Token) (uint64, *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested) { + feeToken := client.Source.WrappedNative.Address() + return client.SendTx(t, from, feeToken, tokens, big.NewInt(100)) +} - sourceBlockNumber := GetCurrentBlockNumber(client.Source.Client.Client) - tx, err := client.Source.Router.CcipSend(from, client.Dest.ChainSelector, msg) - shared.RequireNoError(t, err) - from.Value = big.NewInt(0) - client.Source.logger.Warnf("Message sent for max %d gas %s", tx.Gas(), helpers.ExplorerLink(int64(client.Source.ChainId), tx.Hash())) - return WaitForCrossChainSendRequest(client.Source, sourceBlockNumber, tx.Hash()) +func (client *CCIPClient) sendNativeTx(t *testing.T, from *bind.TransactOpts, tokens []rhea.Token) (uint64, *evm_2_evm_onramp.EVM2EVMOnRampCCIPSendRequested) { + feeToken := common.Address{} + return client.SendTx(t, from, feeToken, tokens, big.NewInt(100)) } func FundPingPong(t *testing.T, chain rhea.EvmDeploymentConfig, minimumBalance *big.Int) { diff --git a/rhea/deploy.go b/rhea/deploy.go index 293272fedc..477a7b0317 100644 --- a/rhea/deploy.go +++ b/rhea/deploy.go @@ -482,3 +482,10 @@ func DeployUpgradePingPongDapps(t *testing.T, sourceClient *EvmDeploymentConfig, func UsdToRateLimitValue(usd int64) *big.Int { return new(big.Int).Mul(big.NewInt(1e18), big.NewInt(usd)) } + +func PercentageOfValue(value *big.Int, percentage int64) (*big.Int, error) { + if percentage < 0 || percentage > 100 { + return nil, fmt.Errorf("percentage must be between 0 and 100") + } + return new(big.Int).Div(new(big.Int).Mul(value, big.NewInt(percentage)), big.NewInt(100)), nil +}