Skip to content

Commit

Permalink
Make basic ccipsend more configurable and add methods to send tokens …
Browse files Browse the repository at this point in the history
…with percentage of rate limiting capacity (smartcontractkit#49)
  • Loading branch information
AnieeG authored Nov 21, 2023
1 parent b74869c commit 924ed86
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 120 deletions.
260 changes: 140 additions & 120 deletions client_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"math/big"
"os"
"strconv"
"sync"
"testing"
Expand All @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -951,160 +1023,108 @@ 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{},
TokenAmounts: []router.ClientEVMTokenAmount{},
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) {
Expand Down
7 changes: 7 additions & 0 deletions rhea/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 924ed86

Please sign in to comment.