Skip to content

Commit

Permalink
[CCIP-5117] integration-tests/smoke/ccip: factor out fee test helpers (
Browse files Browse the repository at this point in the history
…#16496)

* integration-tests/smoke/ccip: factor out fee test helpers

* goimports

* fix comment
  • Loading branch information
makramkd authored Feb 21, 2025
1 parent 944f84d commit c23f77c
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 263 deletions.
2 changes: 1 addition & 1 deletion .github/integration-in-memory-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ runner-test-matrix:
runs_on: ubuntu-latest
triggers:
- PR Integration CCIP Tests
test_cmd: cd integration-tests/smoke/ccip && go test ccip_fees_test.go -timeout 12m -test.parallel=2 -count=1 -json
test_cmd: cd integration-tests/smoke/ccip && go test ccip_fees_test.go -timeout 20m -test.parallel=2 -count=1 -json

- id: smoke/ccip/ccip_messaging_test.go:*
path: integration-tests/smoke/ccip/ccip_messaging_test.go
Expand Down
228 changes: 228 additions & 0 deletions deployment/ccip/changeset/testhelpers/feestest/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package feestest

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/stretchr/testify/require"

"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
"github.com/smartcontractkit/chainlink-integrations/evm/assets"
"github.com/smartcontractkit/chainlink-testing-framework/lib/utils/testcontext"
"github.com/smartcontractkit/chainlink/deployment"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset"
"github.com/smartcontractkit/chainlink/deployment/ccip/changeset/testhelpers"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/v1_2_0/router"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/burn_mint_erc677"
"github.com/smartcontractkit/chainlink/v2/core/gethwrappers/shared/generated/weth9"
)

// NewFeeTokenTestCase creates a new FeeTokenTestCase to test fee token usage scenarios.
func NewFeeTokenTestCase(
t *testing.T,
env deployment.Environment,
src, dst uint64,
feeToken common.Address,
tokenAmounts []router.ClientEVMTokenAmount,
srcToken, dstToken *burn_mint_erc677.BurnMintERC677,
receiver []byte,
data []byte,
assertTokenBalance, assertExecution bool,
) FeeTokenTestCase {
return FeeTokenTestCase{
t: t,
src: src,
dst: dst,
env: env,
srcToken: srcToken,
dstToken: dstToken,
tokenAmounts: tokenAmounts,
feeToken: feeToken,
receiver: receiver,
data: data,
assertTokenBalance: assertTokenBalance,
assertExecution: assertExecution,
}
}

type FeeTokenTestCase struct {
t *testing.T
src, dst uint64
env deployment.Environment
srcToken, dstToken *burn_mint_erc677.BurnMintERC677
tokenAmounts []router.ClientEVMTokenAmount
feeToken common.Address
receiver []byte
data []byte
assertTokenBalance bool
assertExecution bool
}

func RunFeeTokenTestCase(tc FeeTokenTestCase) {
ctx := tests.Context(tc.t)
// Need to keep track of the block number for each chain so that event subscription can be done from that block.
startBlocks := make(map[uint64]*uint64)
expectedSeqNum := make(map[testhelpers.SourceDestPair]uint64)
expectedSeqNumExec := make(map[testhelpers.SourceDestPair][]uint64)

srcChain := tc.env.Chains[tc.src]
dstChain := tc.env.Chains[tc.dst]

state, err := changeset.LoadOnchainState(tc.env)
require.NoError(tc.t, err)

var dstTokBalanceBefore *big.Int
if tc.assertTokenBalance {
var err error
dstTokBalanceBefore, err = tc.dstToken.BalanceOf(nil, state.Chains[tc.dst].Receiver.Address())
require.NoError(tc.t, err)
tc.t.Logf("destination token balance before of receiver %s: %s",
state.Chains[tc.dst].Receiver.Address(),
dstTokBalanceBefore.String())
}

// if fee token is not native then approve the router to spend the fee token from the sender.
var feeTokenWrapper *burn_mint_erc677.BurnMintERC677
if tc.feeToken != common.HexToAddress("0x0") {
if tc.feeToken == state.Chains[tc.src].Weth9.Address() {
// Deposit some ETH into the WETH contract
weth9, err := weth9.NewWETH9(state.Chains[tc.src].Weth9.Address(), srcChain.Client)
require.NoError(tc.t, err)

balance, err := srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil)
require.NoError(tc.t, err)

tc.t.Logf("balance before deposit: %s", balance.String())

srcChain.DeployerKey.Value = assets.Ether(100).ToInt()
tx, err := weth9.Deposit(srcChain.DeployerKey)
_, err = deployment.ConfirmIfNoError(srcChain, tx, err)
require.NoError(tc.t, err)
srcChain.DeployerKey.Value = big.NewInt(0)
}

var err error
feeTokenWrapper, err = burn_mint_erc677.NewBurnMintERC677(tc.feeToken, srcChain.Client)
require.NoError(tc.t, err)

bal, err := feeTokenWrapper.BalanceOf(&bind.CallOpts{
Context: ctx,
}, srcChain.DeployerKey.From)
require.NoError(tc.t, err)

tc.t.Logf("fee token balance before approval: %s", bal.String())

// Approve the router to spend fee token
tx, err := feeTokenWrapper.Approve(srcChain.DeployerKey, state.Chains[tc.src].Router.Address(), math.MaxBig256)

_, err = deployment.ConfirmIfNoError(srcChain, tx, err)
require.NoError(tc.t, err)
}

// get the header for the destination chain and the relevant block number
latesthdr, err := dstChain.Client.HeaderByNumber(testcontext.Get(tc.t), nil)
require.NoError(tc.t, err)
block := latesthdr.Number.Uint64()
startBlocks[tc.dst] = &block

// Get the fee Token Balance Before, if not fee token set get native balance.
var feeTokenBalanceBefore *big.Int
if feeTokenWrapper != nil {
feeTokenBalanceBefore, err = feeTokenWrapper.BalanceOf(&bind.CallOpts{
Context: ctx,
}, srcChain.DeployerKey.From)
require.NoError(tc.t, err)
} else {
feeTokenBalanceBefore, err = srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil)
require.NoError(tc.t, err)
}
tc.t.Logf("fee token balance before: %s, fee token enabled: %s",
feeTokenBalanceBefore.String(), tc.feeToken.String())

msgSentEvent := testhelpers.TestSendRequest(
tc.t,
tc.env,
state,
tc.src,
tc.dst,
false,
router.ClientEVM2AnyMessage{
Receiver: tc.receiver,
Data: tc.data,
TokenAmounts: tc.tokenAmounts,
FeeToken: tc.feeToken,
ExtraArgs: nil,
},
)

expectedSeqNum[testhelpers.SourceDestPair{
SourceChainSelector: tc.src,
DestChainSelector: tc.dst,
}] = msgSentEvent.SequenceNumber
expectedSeqNumExec[testhelpers.SourceDestPair{
SourceChainSelector: tc.src,
DestChainSelector: tc.dst,
}] = []uint64{msgSentEvent.SequenceNumber}

// Check the fee token balance after the request and ensure fee tokens were spent
var feeTokenBalanceAfter *big.Int
if feeTokenWrapper != nil {
feeTokenBalanceAfter, err = feeTokenWrapper.BalanceOf(&bind.CallOpts{
Context: ctx,
}, srcChain.DeployerKey.From)
require.NoError(tc.t, err)
} else {
feeTokenBalanceAfter, err = srcChain.Client.BalanceAt(ctx, srcChain.DeployerKey.From, nil)
require.NoError(tc.t, err)
}
tc.t.Logf("fee token balance after: %s, fee token: %s, fee paid: %s",
feeTokenBalanceAfter.String(), tc.feeToken.String(), msgSentEvent.Message.FeeTokenAmount)
// in the case we have no fee token, native is also used to pay for the tx,
// so we have to subtract that as well
if feeTokenWrapper == nil {
receipt, err := srcChain.Client.TransactionReceipt(ctx, msgSentEvent.Raw.TxHash)
require.NoError(tc.t, err)
txCostWei := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), receipt.EffectiveGasPrice)
feeTokenBalanceBefore.Sub(feeTokenBalanceBefore, txCostWei)
}
require.Equal(
tc.t,
feeTokenBalanceAfter,
new(big.Int).Sub(feeTokenBalanceBefore, msgSentEvent.Message.FeeTokenAmount),
)

if tc.assertExecution {
// Wait for all commit reports to land.
testhelpers.ConfirmCommitForAllWithExpectedSeqNums(tc.t, tc.env, state, expectedSeqNum, startBlocks)

// After commit is reported on all chains, token prices should be updated in FeeQuoter.
linkAddress := state.Chains[tc.dst].LinkToken.Address()
feeQuoter := state.Chains[tc.dst].FeeQuoter
timestampedPrice, err := feeQuoter.GetTokenPrice(&bind.CallOpts{
Context: ctx,
}, linkAddress)
require.NoError(tc.t, err)
require.Equal(tc.t, changeset.MockLinkPrice, timestampedPrice.Value)

// Wait for all exec reports to land
testhelpers.ConfirmExecWithSeqNrsForAll(tc.t, tc.env, state, expectedSeqNumExec, startBlocks)
}

if tc.assertTokenBalance {
require.Len(tc.t, tc.tokenAmounts, 1)
expectedTransferAmount := tc.tokenAmounts[0].Amount

balanceAfter, err := tc.dstToken.BalanceOf(&bind.CallOpts{
Context: ctx,
}, state.Chains[tc.dst].Receiver.Address())
require.NoError(tc.t, err)
require.Equal(
tc.t,
new(big.Int).Add(dstTokBalanceBefore, expectedTransferAmount),
balanceAfter,
)
}
}
5 changes: 1 addition & 4 deletions deployment/ccip/changeset/testhelpers/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,7 @@ func CCIPSendRequest(
}

tx, err := r.CcipSend(cfg.Sender, cfg.DestChain, msg)
if err != nil {
return nil, 0, errors.Wrap(err, "failed to send CCIP message")
}
blockNum, err := e.Chains[cfg.SourceChain].Confirm(tx)
blockNum, err := deployment.ConfirmIfNoErrorWithABI(e.Chains[cfg.SourceChain], tx, router.RouterABI, err)
if err != nil {
return tx, 0, errors.Wrap(err, "failed to confirm CCIP message")
}
Expand Down
Loading

0 comments on commit c23f77c

Please sign in to comment.